mirror of https://github.com/miniflux/v2.git
Add bookmarks
This commit is contained in:
parent
b153fa8b3c
commit
9868f900e9
|
@ -41,7 +41,7 @@
|
|||
branch = "master"
|
||||
name = "github.com/miniflux/miniflux-go"
|
||||
packages = ["."]
|
||||
revision = "ecd111d16e0ce1468cb3b786135c18b3fdc96213"
|
||||
revision = "60d72460e62282aa90cb43fa3a87596900b87678"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tdewolff/minify"
|
||||
|
|
|
@ -36,8 +36,6 @@ type Client struct {
|
|||
|
||||
// Get execute a GET HTTP request.
|
||||
func (c *Client) Get() (*Response, error) {
|
||||
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[HttpClient:Get] url=%s", c.url))
|
||||
|
||||
request, err := c.buildRequest(http.MethodGet, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -22,6 +22,8 @@ const (
|
|||
testAdminPassword = "test123"
|
||||
testStandardPassword = "secret"
|
||||
testFeedURL = "https://github.com/miniflux/miniflux/commits/master.atom"
|
||||
testFeedTitle = "Recent Commits to miniflux:master"
|
||||
testWebsiteURL = "https://github.com/miniflux/miniflux/commits/master"
|
||||
)
|
||||
|
||||
func TestWithBadEndpoint(t *testing.T) {
|
||||
|
@ -486,7 +488,7 @@ func TestCannotDeleteCategoryOfAnotherUser(t *testing.T) {
|
|||
|
||||
func TestDiscoverSubscriptions(t *testing.T) {
|
||||
client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
|
||||
subscriptions, err := client.Discover("https://miniflux.net")
|
||||
subscriptions, err := client.Discover(testWebsiteURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -495,16 +497,16 @@ func TestDiscoverSubscriptions(t *testing.T) {
|
|||
t.Fatalf(`Invalid number of subscriptions, got "%v" instead of "%v"`, len(subscriptions), 2)
|
||||
}
|
||||
|
||||
if subscriptions[0].Title != "Feed" {
|
||||
t.Fatalf(`Invalid feed title, got "%v" instead of "%v"`, subscriptions[0].Title, "Feed")
|
||||
if subscriptions[0].Title != testFeedTitle {
|
||||
t.Fatalf(`Invalid feed title, got "%v" instead of "%v"`, subscriptions[0].Title, testFeedTitle)
|
||||
}
|
||||
|
||||
if subscriptions[0].Type != "atom" {
|
||||
t.Fatalf(`Invalid feed type, got "%v" instead of "%v"`, subscriptions[0].Type, "atom")
|
||||
}
|
||||
|
||||
if subscriptions[0].URL != "https://miniflux.net/feed" {
|
||||
t.Fatalf(`Invalid feed URL, got "%v" instead of "%v"`, subscriptions[0].URL, "https://miniflux.net/feed")
|
||||
if subscriptions[0].URL != testFeedURL {
|
||||
t.Fatalf(`Invalid feed URL, got "%v" instead of "%v"`, subscriptions[0].URL, testFeedURL)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -522,7 +524,7 @@ func TestCreateFeed(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -546,7 +548,7 @@ func TestCannotCreateDuplicatedFeed(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -555,7 +557,7 @@ func TestCannotCreateDuplicatedFeed(t *testing.T) {
|
|||
t.Fatalf(`Invalid feed ID, got "%v"`, feedID)
|
||||
}
|
||||
|
||||
_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
_, err = client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err == nil {
|
||||
t.Fatal(`Duplicated feeds should not be allowed`)
|
||||
}
|
||||
|
@ -570,7 +572,7 @@ func TestCreateFeedWithInexistingCategory(t *testing.T) {
|
|||
}
|
||||
|
||||
client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
|
||||
_, err = client.CreateFeed("https://miniflux.net/feed", -1)
|
||||
_, err = client.CreateFeed(testFeedURL, -1)
|
||||
if err == nil {
|
||||
t.Fatal(`Feeds should not be created with inexisting category`)
|
||||
}
|
||||
|
@ -590,7 +592,7 @@ func TestUpdateFeed(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -630,7 +632,7 @@ func TestDeleteFeed(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -655,7 +657,7 @@ func TestRefreshFeed(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -680,7 +682,7 @@ func TestGetFeed(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -690,16 +692,16 @@ func TestGetFeed(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if feed.Title != "Miniflux" {
|
||||
t.Fatalf(`Invalid feed title, got "%v" instead of "%v"`, feed.Title, "Miniflux")
|
||||
if feed.Title != testFeedTitle {
|
||||
t.Fatalf(`Invalid feed title, got "%v" instead of "%v"`, feed.Title, testFeedTitle)
|
||||
}
|
||||
|
||||
if feed.SiteURL != "https://miniflux.net/" {
|
||||
t.Fatalf(`Invalid site URL, got "%v" instead of "%v"`, feed.SiteURL, "https://miniflux.net/")
|
||||
if feed.SiteURL != testWebsiteURL {
|
||||
t.Fatalf(`Invalid site URL, got "%v" instead of "%v"`, feed.SiteURL, testWebsiteURL)
|
||||
}
|
||||
|
||||
if feed.FeedURL != "https://miniflux.net/feed" {
|
||||
t.Fatalf(`Invalid feed URL, got "%v" instead of "%v"`, feed.FeedURL, "https://miniflux.net/feed")
|
||||
if feed.FeedURL != testFeedURL {
|
||||
t.Fatalf(`Invalid feed URL, got "%v" instead of "%v"`, feed.FeedURL, testFeedURL)
|
||||
}
|
||||
|
||||
if feed.Category.ID != categories[0].ID {
|
||||
|
@ -780,7 +782,7 @@ func TestGetFeeds(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -798,16 +800,16 @@ func TestGetFeeds(t *testing.T) {
|
|||
t.Fatalf(`Invalid feed ID, got "%v" instead of "%v"`, feeds[0].ID, feedID)
|
||||
}
|
||||
|
||||
if feeds[0].Title != "Miniflux" {
|
||||
t.Fatalf(`Invalid feed title, got "%v" instead of "%v"`, feeds[0].Title, "Miniflux")
|
||||
if feeds[0].Title != testFeedTitle {
|
||||
t.Fatalf(`Invalid feed title, got "%v" instead of "%v"`, feeds[0].Title, testFeedTitle)
|
||||
}
|
||||
|
||||
if feeds[0].SiteURL != "https://miniflux.net/" {
|
||||
t.Fatalf(`Invalid site URL, got "%v" instead of "%v"`, feeds[0].SiteURL, "https://miniflux.net/")
|
||||
if feeds[0].SiteURL != testWebsiteURL {
|
||||
t.Fatalf(`Invalid site URL, got "%v" instead of "%v"`, feeds[0].SiteURL, testWebsiteURL)
|
||||
}
|
||||
|
||||
if feeds[0].FeedURL != "https://miniflux.net/feed" {
|
||||
t.Fatalf(`Invalid feed URL, got "%v" instead of "%v"`, feeds[0].FeedURL, "https://miniflux.net/feed")
|
||||
if feeds[0].FeedURL != testFeedURL {
|
||||
t.Fatalf(`Invalid feed URL, got "%v" instead of "%v"`, feeds[0].FeedURL, testFeedURL)
|
||||
}
|
||||
|
||||
if feeds[0].Category.ID != categories[0].ID {
|
||||
|
@ -837,7 +839,7 @@ func TestGetAllFeedEntries(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
feedID, err := client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -883,7 +885,7 @@ func TestGetAllEntries(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
_, err = client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -930,7 +932,7 @@ func TestInvalidFilters(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
_, err = client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -965,7 +967,7 @@ func TestGetEntry(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
_, err = client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -975,7 +977,16 @@ func TestGetEntry(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entry, err := client.Entry(result.Entries[0].FeedID, result.Entries[0].ID)
|
||||
entry, err := client.FeedEntry(result.Entries[0].FeedID, result.Entries[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if entry.ID != result.Entries[0].ID {
|
||||
t.Fatal("Wrong entry returned")
|
||||
}
|
||||
|
||||
entry, err = client.Entry(result.Entries[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -999,7 +1010,7 @@ func TestUpdateStatus(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
|
||||
_, err = client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -1014,7 +1025,7 @@ func TestUpdateStatus(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entry, err := client.Entry(result.Entries[0].FeedID, result.Entries[0].ID)
|
||||
entry, err := client.Entry(result.Entries[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -1029,6 +1040,49 @@ func TestUpdateStatus(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestToggleBookmark(t *testing.T) {
|
||||
username := getRandomUsername()
|
||||
client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
|
||||
_, err := client.CreateUser(username, testStandardPassword, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
|
||||
categories, err := client.Categories()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.CreateFeed(testFeedURL, categories[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result, err := client.Entries(&miniflux.Filter{Limit: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result.Entries[0].Starred {
|
||||
t.Fatal("The entry should not be starred")
|
||||
}
|
||||
|
||||
err = client.ToggleBookmark(result.Entries[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entry, err := client.Entry(result.Entries[0].ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !entry.Starred {
|
||||
t.Fatal("The entry should be starred")
|
||||
}
|
||||
}
|
||||
|
||||
func getRandomUsername() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
var suffix []string
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-18 18:49:32.159555255 -0800 PST m=+0.041213049
|
||||
// 2017-12-22 11:25:01.98320223 -0800 PST m=+0.048169992
|
||||
|
||||
package locale
|
||||
|
||||
|
@ -177,12 +177,18 @@ var translations = map[string]string{
|
|||
"Wallabag Client ID": "Identifiant du client Wallabag",
|
||||
"Wallabag Client Secret": "Clé secrète du client Wallabag",
|
||||
"Wallabag Username": "Nom d'utilisateur de Wallabag",
|
||||
"Wallabag Password": "Mot de passe de Wallabag"
|
||||
"Wallabag Password": "Mot de passe de Wallabag",
|
||||
"Keyboard Shortcut: %s": "Raccourci clavier : %s",
|
||||
"Favorites": "Favoris",
|
||||
"Star": "Favoris",
|
||||
"Unstar": "Enlever favoris",
|
||||
"Starred": "Favoris",
|
||||
"There is no bookmark at the moment.": "Il n'y a aucun favoris pour le moment."
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
||||
var translationsChecksums = map[string]string{
|
||||
"en_US": "6fe95384260941e8a5a3c695a655a932e0a8a6a572c1e45cb2b1ae8baa01b897",
|
||||
"fr_FR": "3a71dbf4fcdb488acdaf43530e521a0c17a28ef637fbd60b204e468afb0dbe09",
|
||||
"fr_FR": "e6817ae43e1412d2687036fb4c1b9f6ea4a2329dcb1eddfa01ebbad732c7b401",
|
||||
}
|
||||
|
|
|
@ -161,5 +161,11 @@
|
|||
"Wallabag Client ID": "Identifiant du client Wallabag",
|
||||
"Wallabag Client Secret": "Clé secrète du client Wallabag",
|
||||
"Wallabag Username": "Nom d'utilisateur de Wallabag",
|
||||
"Wallabag Password": "Mot de passe de Wallabag"
|
||||
"Wallabag Password": "Mot de passe de Wallabag",
|
||||
"Keyboard Shortcut: %s": "Raccourci clavier : %s",
|
||||
"Favorites": "Favoris",
|
||||
"Star": "Favoris",
|
||||
"Unstar": "Enlever favoris",
|
||||
"Starred": "Favoris",
|
||||
"There is no bookmark at the moment.": "Il n'y a aucun favoris pour le moment."
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ type Entry struct {
|
|||
Date time.Time `json:"published_at"`
|
||||
Content string `json:"content"`
|
||||
Author string `json:"author"`
|
||||
Starred bool `json:"starred"`
|
||||
Enclosures EnclosureList `json:"enclosures,omitempty"`
|
||||
Feed *Feed `json:"feed,omitempty"`
|
||||
Category *Category `json:"category,omitempty"`
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"github.com/miniflux/miniflux/server/core"
|
||||
)
|
||||
|
||||
// GetEntry is the API handler to get a single feed entry.
|
||||
func (c *Controller) GetEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
// GetFeedEntry is the API handler to get a single feed entry.
|
||||
func (c *Controller) GetFeedEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
|
@ -45,6 +45,32 @@ func (c *Controller) GetEntry(ctx *core.Context, request *core.Request, response
|
|||
response.JSON().Standard(entry)
|
||||
}
|
||||
|
||||
// GetEntry is the API handler to get a single entry.
|
||||
func (c *Controller) GetEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
userID := ctx.UserID()
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(userID, ctx.UserTimezone())
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch this entry from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.JSON().NotFound(errors.New("Entry not found"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(entry)
|
||||
}
|
||||
|
||||
// GetFeedEntries is the API handler to get all feed entries.
|
||||
func (c *Controller) GetFeedEntries(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
userID := ctx.UserID()
|
||||
|
@ -179,3 +205,20 @@ func (c *Controller) SetEntryStatus(ctx *core.Context, request *core.Request, re
|
|||
|
||||
response.JSON().NoContent()
|
||||
}
|
||||
|
||||
// ToggleBookmark is the API handler to toggle bookmark status.
|
||||
func (c *Controller) ToggleBookmark(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
userID := ctx.UserID()
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.ToggleBookmark(userID, entryID); err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to toggle bookmark value"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().NoContent()
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ type savedResponse struct {
|
|||
|
||||
type linksResponse struct {
|
||||
baseResponse
|
||||
Links []string `json:"links"`
|
||||
Links string `json:"links"`
|
||||
}
|
||||
|
||||
type group struct {
|
||||
|
@ -242,6 +242,7 @@ func (c *Controller) handleFeeds(ctx *core.Context, request *core.Request, respo
|
|||
}
|
||||
|
||||
var result feedsResponse
|
||||
result.Feeds = make([]feed, 0)
|
||||
for _, f := range feeds {
|
||||
result.Feeds = append(result.Feeds, feed{
|
||||
ID: f.ID,
|
||||
|
@ -387,6 +388,11 @@ func (c *Controller) handleItems(ctx *core.Context, request *core.Request, respo
|
|||
isRead = 1
|
||||
}
|
||||
|
||||
isSaved := 0
|
||||
if entry.Starred {
|
||||
isSaved = 1
|
||||
}
|
||||
|
||||
result.Items = append(result.Items, item{
|
||||
ID: entry.ID,
|
||||
FeedID: entry.FeedID,
|
||||
|
@ -394,7 +400,7 @@ func (c *Controller) handleItems(ctx *core.Context, request *core.Request, respo
|
|||
Author: entry.Author,
|
||||
HTML: entry.Content,
|
||||
URL: entry.URL,
|
||||
IsSaved: 0,
|
||||
IsSaved: isSaved,
|
||||
IsRead: isRead,
|
||||
CreatedAt: entry.Date.Unix(),
|
||||
})
|
||||
|
@ -446,7 +452,21 @@ func (c *Controller) handleSavedItems(ctx *core.Context, request *core.Request,
|
|||
userID := ctx.UserID()
|
||||
logger.Debug("[Fever] Fetching saved items for userID=%d", userID)
|
||||
|
||||
var result savedResponse
|
||||
builder := c.store.GetEntryQueryBuilder(userID, ctx.UserTimezone())
|
||||
builder.WithStarred()
|
||||
|
||||
entryIDs, err := builder.GetEntryIDs()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
var itemsIDs []string
|
||||
for _, entryID := range entryIDs {
|
||||
itemsIDs = append(itemsIDs, strconv.FormatInt(entryID, 10))
|
||||
}
|
||||
|
||||
result := &savedResponse{ItemIDs: strings.Join(itemsIDs, ",")}
|
||||
result.SetCommonValues()
|
||||
response.JSON().Standard(result)
|
||||
}
|
||||
|
@ -473,7 +493,7 @@ func (c *Controller) handleLinks(ctx *core.Context, request *core.Request, respo
|
|||
userID := ctx.UserID()
|
||||
logger.Debug("[Fever] Fetching links for userID=%d", userID)
|
||||
|
||||
var result linksResponse
|
||||
result := &linksResponse{Links: ""}
|
||||
result.SetCommonValues()
|
||||
response.JSON().Standard(result)
|
||||
}
|
||||
|
@ -512,6 +532,11 @@ func (c *Controller) handleWriteItems(ctx *core.Context, request *core.Request,
|
|||
case "unread":
|
||||
c.store.SetEntriesStatus(userID, []int64{entryID}, model.EntryStatusUnread)
|
||||
case "saved":
|
||||
if err := c.store.ToggleBookmark(userID, entryID); err != nil {
|
||||
response.JSON().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := c.store.Integration(userID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(err)
|
||||
|
@ -619,7 +644,7 @@ func (c *Controller) buildFeedGroups(feeds model.Feeds) []feedsGroups {
|
|||
feedsGroupedByCategory[feed.Category.ID] = append(feedsGroupedByCategory[feed.Category.ID], strconv.FormatInt(feed.ID, 10))
|
||||
}
|
||||
|
||||
var result []feedsGroups
|
||||
result := make([]feedsGroups, 0)
|
||||
for categoryID, feedIDs := range feedsGroupedByCategory {
|
||||
result = append(result, feedsGroups{
|
||||
GroupID: categoryID,
|
||||
|
|
|
@ -70,9 +70,11 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
|
|||
router.Handle("/v1/feeds/{feedID}/icon", apiHandler.Use(apiController.FeedIcon)).Methods("GET")
|
||||
|
||||
router.Handle("/v1/feeds/{feedID}/entries", apiHandler.Use(apiController.GetFeedEntries)).Methods("GET")
|
||||
router.Handle("/v1/feeds/{feedID}/entries/{entryID}", apiHandler.Use(apiController.GetEntry)).Methods("GET")
|
||||
router.Handle("/v1/feeds/{feedID}/entries/{entryID}", apiHandler.Use(apiController.GetFeedEntry)).Methods("GET")
|
||||
router.Handle("/v1/entries", apiHandler.Use(apiController.GetEntries)).Methods("GET")
|
||||
router.Handle("/v1/entries", apiHandler.Use(apiController.SetEntryStatus)).Methods("PUT")
|
||||
router.Handle("/v1/entries/{entryID}", apiHandler.Use(apiController.GetEntry)).Methods("GET")
|
||||
router.Handle("/v1/entries/{entryID}/bookmark", apiHandler.Use(apiController.ToggleBookmark)).Methods("PUT")
|
||||
|
||||
router.Handle("/stylesheets/{name}.css", uiHandler.Use(uiController.Stylesheet)).Name("stylesheet").Methods("GET")
|
||||
router.Handle("/js", uiHandler.Use(uiController.Javascript)).Name("javascript").Methods("GET")
|
||||
|
@ -85,6 +87,7 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
|
|||
|
||||
router.Handle("/unread", uiHandler.Use(uiController.ShowUnreadPage)).Name("unread").Methods("GET")
|
||||
router.Handle("/history", uiHandler.Use(uiController.ShowHistoryPage)).Name("history").Methods("GET")
|
||||
router.Handle("/starred", uiHandler.Use(uiController.ShowStarredPage)).Name("starred").Methods("GET")
|
||||
|
||||
router.Handle("/feed/{feedID}/refresh", uiHandler.Use(uiController.RefreshFeed)).Name("refreshFeed").Methods("GET")
|
||||
router.Handle("/feed/{feedID}/edit", uiHandler.Use(uiController.EditFeed)).Name("editFeed").Methods("GET")
|
||||
|
@ -99,10 +102,12 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
|
|||
router.Handle("/history/flush", uiHandler.Use(uiController.FlushHistory)).Name("flushHistory").Methods("GET")
|
||||
router.Handle("/feed/{feedID}/entry/{entryID}", uiHandler.Use(uiController.ShowFeedEntry)).Name("feedEntry").Methods("GET")
|
||||
router.Handle("/category/{categoryID}/entry/{entryID}", uiHandler.Use(uiController.ShowCategoryEntry)).Name("categoryEntry").Methods("GET")
|
||||
router.Handle("/starred/entry/{entryID}", uiHandler.Use(uiController.ShowStarredEntry)).Name("starredEntry").Methods("GET")
|
||||
|
||||
router.Handle("/entry/status", uiHandler.Use(uiController.UpdateEntriesStatus)).Name("updateEntriesStatus").Methods("POST")
|
||||
router.Handle("/entry/save/{entryID}", uiHandler.Use(uiController.SaveEntry)).Name("saveEntry").Methods("POST")
|
||||
router.Handle("/entry/download/{entryID}", uiHandler.Use(uiController.FetchContent)).Name("fetchContent").Methods("POST")
|
||||
router.Handle("/entry/bookmark/{entryID}", uiHandler.Use(uiController.ToggleBookmark)).Name("toggleBookmark").Methods("POST")
|
||||
|
||||
router.Handle("/categories", uiHandler.Use(uiController.ShowCategories)).Name("categories").Methods("GET")
|
||||
router.Handle("/category/create", uiHandler.Use(uiController.CreateCategory)).Name("createCategory").Methods("GET")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-15 21:24:38.374067217 -0800 PST m=+0.003159627
|
||||
// 2017-12-22 11:25:01.957187237 -0800 PST m=+0.022154999
|
||||
|
||||
package static
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-15 18:49:24.040014054 -0800 PST m=+0.012609926
|
||||
// 2017-12-22 11:25:01.96382557 -0800 PST m=+0.028793332
|
||||
|
||||
package static
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-15 18:49:24.041876548 -0800 PST m=+0.014472420
|
||||
// 2017-12-22 11:25:01.967857672 -0800 PST m=+0.032825434
|
||||
|
||||
package static
|
||||
|
||||
|
@ -43,6 +43,7 @@ return "";}
|
|||
execute(){fetch(new Request(this.url,this.options)).then((response)=>{if(this.callback){this.callback(response);}});}}
|
||||
class EntryHandler{static updateEntriesStatus(entryIDs,status,callback){let url=document.body.dataset.entriesStatusUrl;let request=new RequestBuilder(url);request.withBody({entry_ids:entryIDs,status:status});request.withCallback(callback);request.execute();}
|
||||
static toggleEntryStatus(element){let entryID=parseInt(element.dataset.id,10);let statuses={read:"unread",unread:"read"};for(let currentStatus in statuses){let newStatus=statuses[currentStatus];if(element.classList.contains("item-status-"+currentStatus)){element.classList.remove("item-status-"+currentStatus);element.classList.add("item-status-"+newStatus);this.updateEntriesStatus([entryID],newStatus);break;}}}
|
||||
static toggleBookmark(element){element.innerHTML=element.dataset.labelLoading;let request=new RequestBuilder(element.dataset.bookmarkUrl);request.withCallback(()=>{if(element.dataset.value==="star"){element.innerHTML=element.dataset.labelStar;element.dataset.value="unstar";}else{element.innerHTML=element.dataset.labelUnstar;element.dataset.value="star";}});request.execute();}
|
||||
static markEntryAsRead(element){if(element.classList.contains("item-status-unread")){element.classList.remove("item-status-unread");element.classList.add("item-status-read");let entryID=parseInt(element.dataset.id,10);this.updateEntriesStatus([entryID],"read");}}
|
||||
static saveEntry(element){if(element.dataset.completed){return;}
|
||||
element.innerHTML=element.dataset.labelLoading;let request=new RequestBuilder(element.dataset.saveUrl);request.withCallback(()=>{element.innerHTML=element.dataset.labelDone;element.dataset.completed=true;});request.execute();}
|
||||
|
@ -56,6 +57,9 @@ class NavHandler{markPageAsRead(){let items=DomHelper.getVisibleElements(".items
|
|||
saveEntry(){if(this.isListView()){let currentItem=document.querySelector(".current-item");if(currentItem!==null){let saveLink=currentItem.querySelector("a[data-save-entry]");if(saveLink){EntryHandler.saveEntry(saveLink);}}}else{let saveLink=document.querySelector("a[data-save-entry]");if(saveLink){EntryHandler.saveEntry(saveLink);}}}
|
||||
fetchOriginalContent(){if(!this.isListView()){let link=document.querySelector("a[data-fetch-content-entry]");if(link){EntryHandler.fetchOriginalContent(link);}}}
|
||||
toggleEntryStatus(){let currentItem=document.querySelector(".current-item");if(currentItem!==null){this.goToNextListItem();EntryHandler.toggleEntryStatus(currentItem);}}
|
||||
toggleBookmark(){if(!this.isListView()){this.toggleBookmarkLink(document.querySelector(".entry"));return;}
|
||||
let currentItem=document.querySelector(".current-item");if(currentItem!==null){this.toggleBookmarkLink(currentItem);}}
|
||||
toggleBookmarkLink(parent){let bookmarkLink=parent.querySelector("a[data-toggle-bookmark]");if(bookmarkLink){EntryHandler.toggleBookmark(bookmarkLink);}}
|
||||
openOriginalLink(){let entryLink=document.querySelector(".entry h1 a");if(entryLink!==null){DomHelper.openNewTab(entryLink.getAttribute("href"));return;}
|
||||
let currentItemOriginalLink=document.querySelector(".current-item a[data-original-link]");if(currentItemOriginalLink!==null){DomHelper.openNewTab(currentItemOriginalLink.getAttribute("href"));let currentItem=document.querySelector(".current-item");this.goToNextListItem();EntryHandler.markEntryAsRead(currentItem);}}
|
||||
openSelectedItem(){let currentItemLink=document.querySelector(".current-item .item-title a");if(currentItemLink!==null){window.location.href=currentItemLink.getAttribute("href");}}
|
||||
|
@ -71,9 +75,9 @@ if(currentItem===null){items[0].classList.add("current-item");return;}
|
|||
for(let i=0;i<items.length;i++){if(items[i].classList.contains("current-item")){items[i].classList.remove("current-item");if(i+1<items.length){items[i+1].classList.add("current-item");DomHelper.scrollPageTo(items[i+1]);}
|
||||
break;}}}
|
||||
isListView(){return document.querySelector(".items")!==null;}}
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let touchHandler=new TouchHandler();touchHandler.listen();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToPage("feeds"));keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.on("s",()=>navHandler.saveEntry());keyboardHandler.on("d",()=>navHandler.fetchOriginalContent());keyboardHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-save-entry]",(event)=>{event.preventDefault();EntryHandler.saveEntry(event.target);});mouseHandler.onClick("a[data-fetch-content-entry]",(event)=>{event.preventDefault();EntryHandler.fetchOriginalContent(event.target);});mouseHandler.onClick("a[data-on-click=markPageAsRead]",()=>navHandler.markPageAsRead());mouseHandler.onClick("a[data-confirm]",(event)=>{(new ConfirmHandler()).handle(event);});if(document.documentElement.clientWidth<600){let menuHandler=new MenuHandler();mouseHandler.onClick(".logo",()=>menuHandler.toggleMainMenu());mouseHandler.onClick(".header nav li",(event)=>menuHandler.clickMenuListItem(event));}});})();`,
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let touchHandler=new TouchHandler();touchHandler.listen();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g b",()=>navHandler.goToPage("starred"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToPage("feeds"));keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.on("s",()=>navHandler.saveEntry());keyboardHandler.on("d",()=>navHandler.fetchOriginalContent());keyboardHandler.on("f",()=>navHandler.toggleBookmark());keyboardHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-save-entry]",(event)=>{event.preventDefault();EntryHandler.saveEntry(event.target);});mouseHandler.onClick("a[data-toggle-bookmark]",(event)=>{event.preventDefault();EntryHandler.toggleBookmark(event.target);});mouseHandler.onClick("a[data-fetch-content-entry]",(event)=>{event.preventDefault();EntryHandler.fetchOriginalContent(event.target);});mouseHandler.onClick("a[data-on-click=markPageAsRead]",()=>navHandler.markPageAsRead());mouseHandler.onClick("a[data-confirm]",(event)=>{(new ConfirmHandler()).handle(event);});if(document.documentElement.clientWidth<600){let menuHandler=new MenuHandler();mouseHandler.onClick(".logo",()=>menuHandler.toggleMainMenu());mouseHandler.onClick(".header nav li",(event)=>menuHandler.clickMenuListItem(event));}});})();`,
|
||||
}
|
||||
|
||||
var JavascriptChecksums = map[string]string{
|
||||
"app": "a70092cda52d5c3673e789868d8cfeb73a890e1a931b102a738021b5c2a65519",
|
||||
"app": "835ca386dadfc0a7fc3aa6000419051bb8f99f23653c875423f79ff037dcd2da",
|
||||
}
|
||||
|
|
|
@ -300,6 +300,22 @@ class EntryHandler {
|
|||
}
|
||||
}
|
||||
|
||||
static toggleBookmark(element) {
|
||||
element.innerHTML = element.dataset.labelLoading;
|
||||
|
||||
let request = new RequestBuilder(element.dataset.bookmarkUrl);
|
||||
request.withCallback(() => {
|
||||
if (element.dataset.value === "star") {
|
||||
element.innerHTML = element.dataset.labelStar;
|
||||
element.dataset.value = "unstar";
|
||||
} else {
|
||||
element.innerHTML = element.dataset.labelUnstar;
|
||||
element.dataset.value = "star";
|
||||
}
|
||||
});
|
||||
request.execute();
|
||||
}
|
||||
|
||||
static markEntryAsRead(element) {
|
||||
if (element.classList.contains("item-status-unread")) {
|
||||
element.classList.remove("item-status-unread");
|
||||
|
@ -468,6 +484,25 @@ class NavHandler {
|
|||
}
|
||||
}
|
||||
|
||||
toggleBookmark() {
|
||||
if (! this.isListView()) {
|
||||
this.toggleBookmarkLink(document.querySelector(".entry"));
|
||||
return;
|
||||
}
|
||||
|
||||
let currentItem = document.querySelector(".current-item");
|
||||
if (currentItem !== null) {
|
||||
this.toggleBookmarkLink(currentItem);
|
||||
}
|
||||
}
|
||||
|
||||
toggleBookmarkLink(parent) {
|
||||
let bookmarkLink = parent.querySelector("a[data-toggle-bookmark]");
|
||||
if (bookmarkLink) {
|
||||
EntryHandler.toggleBookmark(bookmarkLink);
|
||||
}
|
||||
}
|
||||
|
||||
openOriginalLink() {
|
||||
let entryLink = document.querySelector(".entry h1 a");
|
||||
if (entryLink !== null) {
|
||||
|
@ -588,6 +623,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
let navHandler = new NavHandler();
|
||||
let keyboardHandler = new KeyboardHandler();
|
||||
keyboardHandler.on("g u", () => navHandler.goToPage("unread"));
|
||||
keyboardHandler.on("g b", () => navHandler.goToPage("starred"));
|
||||
keyboardHandler.on("g h", () => navHandler.goToPage("history"));
|
||||
keyboardHandler.on("g f", () => navHandler.goToPage("feeds"));
|
||||
keyboardHandler.on("g c", () => navHandler.goToPage("categories"));
|
||||
|
@ -606,6 +642,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
keyboardHandler.on("A", () => navHandler.markPageAsRead());
|
||||
keyboardHandler.on("s", () => navHandler.saveEntry());
|
||||
keyboardHandler.on("d", () => navHandler.fetchOriginalContent());
|
||||
keyboardHandler.on("f", () => navHandler.toggleBookmark());
|
||||
keyboardHandler.listen();
|
||||
|
||||
let mouseHandler = new MouseHandler();
|
||||
|
@ -614,6 +651,11 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
EntryHandler.saveEntry(event.target);
|
||||
});
|
||||
|
||||
mouseHandler.onClick("a[data-toggle-bookmark]", (event) => {
|
||||
event.preventDefault();
|
||||
EntryHandler.toggleBookmark(event.target);
|
||||
});
|
||||
|
||||
mouseHandler.onClick("a[data-fetch-content-entry]", (event) => {
|
||||
event.preventDefault();
|
||||
EntryHandler.fetchOriginalContent(event.target);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-16 17:48:32.321995978 -0800 PST m=+0.055632657
|
||||
// 2017-12-22 11:25:01.981502305 -0800 PST m=+0.046470067
|
||||
|
||||
package template
|
||||
|
||||
|
@ -63,22 +63,25 @@ var templateCommonMap = map[string]string{
|
|||
<a href="{{ route "unread" }}">Mini<span>flux</span></a>
|
||||
</div>
|
||||
<ul>
|
||||
<li {{ if eq .menu "unread" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "unread" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g u" }}">
|
||||
<a href="{{ route "unread" }}" data-page="unread">{{ t "Unread" }}</a>
|
||||
{{ if gt .countUnread 0 }}
|
||||
<span class="unread-counter" title="Unread articles">({{ .countUnread }})</span>
|
||||
{{ end }}
|
||||
</li>
|
||||
<li {{ if eq .menu "history" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "starred" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g b" }}">
|
||||
<a href="{{ route "starred" }}" data-page="starred">{{ t "Starred" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "history" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g h" }}">
|
||||
<a href="{{ route "history" }}" data-page="history">{{ t "History" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "feeds" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "feeds" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g f" }}">
|
||||
<a href="{{ route "feeds" }}" data-page="feeds">{{ t "Feeds" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "categories" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "categories" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g c" }}">
|
||||
<a href="{{ route "categories" }}" data-page="categories">{{ t "Categories" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "settings" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "settings" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g s" }}">
|
||||
<a href="{{ route "settings" }}" data-page="settings">{{ t "Settings" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -124,6 +127,6 @@ var templateCommonMap = map[string]string{
|
|||
|
||||
var templateCommonMapChecksums = map[string]string{
|
||||
"entry_pagination": "f1465fa70f585ae8043b200ec9de5bf437ffbb0c19fb7aefc015c3555614ee27",
|
||||
"layout": "ff5e3d87a48e4d3aeceda4aabe6c2c2f607006c6b6e83dfcab6c5eb255a1e6f2",
|
||||
"layout": "ade38fbe1058c8dac86b973c289a716e3f97289735e7ad8e8d1731dc6807e38c",
|
||||
"pagination": "6ff462c2b2a53bc5448b651da017f40a39f1d4f16cef4b2f09784f0797286924",
|
||||
}
|
||||
|
|
|
@ -47,6 +47,16 @@
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
@ -38,22 +38,25 @@
|
|||
<a href="{{ route "unread" }}">Mini<span>flux</span></a>
|
||||
</div>
|
||||
<ul>
|
||||
<li {{ if eq .menu "unread" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "unread" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g u" }}">
|
||||
<a href="{{ route "unread" }}" data-page="unread">{{ t "Unread" }}</a>
|
||||
{{ if gt .countUnread 0 }}
|
||||
<span class="unread-counter" title="Unread articles">({{ .countUnread }})</span>
|
||||
{{ end }}
|
||||
</li>
|
||||
<li {{ if eq .menu "history" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "starred" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g b" }}">
|
||||
<a href="{{ route "starred" }}" data-page="starred">{{ t "Starred" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "history" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g h" }}">
|
||||
<a href="{{ route "history" }}" data-page="history">{{ t "History" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "feeds" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "feeds" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g f" }}">
|
||||
<a href="{{ route "feeds" }}" data-page="feeds">{{ t "Feeds" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "categories" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "categories" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g c" }}">
|
||||
<a href="{{ route "categories" }}" data-page="categories">{{ t "Categories" }}</a>
|
||||
</li>
|
||||
<li {{ if eq .menu "settings" }}class="active"{{ end }}>
|
||||
<li {{ if eq .menu "settings" }}class="active"{{ end }} title="{{ t "Keyboard Shortcut: %s" "g s" }}">
|
||||
<a href="{{ route "settings" }}" data-page="settings">{{ t "Settings" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
@ -8,6 +8,16 @@
|
|||
</h1>
|
||||
<div class="entry-actions">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .entry.ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .entry.Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
|
|
|
@ -58,6 +58,16 @@
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
@ -47,6 +47,16 @@
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
{{ define "title"}}{{ t "Favorites" }} ({{ .total }}){{ end }}
|
||||
|
||||
{{ define "content"}}
|
||||
<section class="page-header">
|
||||
<h1>{{ t "Favorites" }} ({{ .total }})</h1>
|
||||
</section>
|
||||
|
||||
{{ if not .entries }}
|
||||
<p class="alert alert-info">{{ t "There is no bookmark at the moment." }}</p>
|
||||
{{ else }}
|
||||
<div class="items">
|
||||
{{ range .entries }}
|
||||
<article class="item touch-item item-status-{{ .Status }}" data-id="{{ .ID }}">
|
||||
<div class="item-header">
|
||||
<span class="item-title">
|
||||
{{ if ne .Feed.Icon.IconID 0 }}
|
||||
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16">
|
||||
{{ end }}
|
||||
<a href="{{ route "starredEntry" "entryID" .ID }}">{{ .Title }}</a>
|
||||
</span>
|
||||
<span class="category"><a href="{{ route "categoryEntries" "categoryID" .Feed.Category.ID }}">{{ .Feed.Category.Title }}</a></span>
|
||||
</div>
|
||||
<div class="item-meta">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ route "feedEntries" "feedID" .Feed.ID }}" title="{{ .Feed.Title }}">{{ domain .Feed.SiteURL }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ template "pagination" .pagination }}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
|
@ -47,6 +47,16 @@
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-18 18:49:32.144679579 -0800 PST m=+0.026337373
|
||||
// 2017-12-22 11:25:01.96909666 -0800 PST m=+0.034064422
|
||||
|
||||
package template
|
||||
|
||||
|
@ -199,6 +199,16 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
@ -480,6 +490,16 @@ var templateViewsMap = map[string]string{
|
|||
</h1>
|
||||
<div class="entry-actions">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .entry.ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .entry.Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
|
@ -630,6 +650,16 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
@ -764,6 +794,16 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
@ -1084,6 +1124,68 @@ var templateViewsMap = map[string]string{
|
|||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
`,
|
||||
"starred": `{{ define "title"}}{{ t "Favorites" }} ({{ .total }}){{ end }}
|
||||
|
||||
{{ define "content"}}
|
||||
<section class="page-header">
|
||||
<h1>{{ t "Favorites" }} ({{ .total }})</h1>
|
||||
</section>
|
||||
|
||||
{{ if not .entries }}
|
||||
<p class="alert alert-info">{{ t "There is no bookmark at the moment." }}</p>
|
||||
{{ else }}
|
||||
<div class="items">
|
||||
{{ range .entries }}
|
||||
<article class="item touch-item item-status-{{ .Status }}" data-id="{{ .ID }}">
|
||||
<div class="item-header">
|
||||
<span class="item-title">
|
||||
{{ if ne .Feed.Icon.IconID 0 }}
|
||||
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16">
|
||||
{{ end }}
|
||||
<a href="{{ route "starredEntry" "entryID" .ID }}">{{ .Title }}</a>
|
||||
</span>
|
||||
<span class="category"><a href="{{ route "categoryEntries" "categoryID" .Feed.Category.ID }}">{{ .Feed.Category.Title }}</a></span>
|
||||
</div>
|
||||
<div class="item-meta">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ route "feedEntries" "feedID" .Feed.ID }}" title="{{ .Feed.Title }}">{{ domain .Feed.SiteURL }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ template "pagination" .pagination }}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
`,
|
||||
"unread": `{{ define "title"}}{{ t "Unread Items" }} {{ if gt .countUnread 0 }}({{ .countUnread }}){{ end }} {{ end }}
|
||||
|
@ -1135,6 +1237,16 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
data-toggle-bookmark="true"
|
||||
data-bookmark-url="{{ route "toggleBookmark" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-star="☆ {{ t "Star" }}"
|
||||
data-label-unstar="★ {{ t "Unstar" }}"
|
||||
data-value="{{ if .Starred }}star{{ else }}unstar{{ end }}"
|
||||
>{{ if .Starred }}★ {{ t "Unstar" }}{{ else }}☆ {{ t "Star" }}{{ end }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
@ -1211,22 +1323,23 @@ var templateViewsMapChecksums = map[string]string{
|
|||
"about": "ad2fb778fc73c39b733b3f81b13e5c7d689b041fadd24ee2d4577f545aa788ad",
|
||||
"add_subscription": "053c920b0d7e109ea19dce6a448e304ce720db8633588ea04db16677f7209a7b",
|
||||
"categories": "ca1280cd157bb527d4fc907da67b05a8347378f6dce965b9389d4bcdf3600a11",
|
||||
"category_entries": "951cdacf38fcaed5cdd63a00dc800e26039236b94b556a68e4409012b0095ece",
|
||||
"category_entries": "ce59529666520b8363c9588ce2c437de5a3f6d91941e5c46be25ca08f6900364",
|
||||
"choose_subscription": "a325f9c976ca2b2dc148e25c8fef0cf6ccab0e04e86e604e7812bb18dc4cdde1",
|
||||
"create_category": "2b82af5d2dcd67898dc5daa57a6461e6ff8121a6089b2a2a1be909f35e4a2275",
|
||||
"create_user": "45e226df757126d5fe7c464e295e9a34f07952cfdb71e31e49839850d35af139",
|
||||
"edit_category": "cee720faadcec58289b707ad30af623d2ee66c1ce23a732965463250d7ff41c5",
|
||||
"edit_feed": "7e78f0821312557ca05eb840fd52bcb60509c6da205e8ffce11eb08f65ae143d",
|
||||
"edit_user": "82d9749d76ddbd2352816d813c4b1f6d92f2222de678b4afe5821090246735c7",
|
||||
"entry": "ebcf9bb35812dd02759718f7f7411267e6a6c8efd59a9aa0a0e735bcb88efeff",
|
||||
"feed_entries": "547c19eb36b20e350ce70ed045173b064cdcd6b114afb241c9f2dda9d88fcc27",
|
||||
"entry": "6b4405e0c8e4a7d31874659f8835f4e43e01dc3c20686091517ac750196dd70f",
|
||||
"feed_entries": "ac93cb9a90f93ddd9dd8a67d7e160592ecb9f5e465ee9679bb14eecd8d4caf20",
|
||||
"feeds": "c22af39b42ba9ca69ea0914ca789303ec2c5b484abcd4eaa49016e365381257c",
|
||||
"history": "9a67599a5d8d67ef958e3f07da339b749f42892667547c9e60a54477e8d32a56",
|
||||
"history": "abc7ea29f7d54f28f73fe14979bbd03dbc41fa6a7c86f95f56d6e94f7b09b9ba",
|
||||
"import": "73b5112e20bfd232bf73334544186ea419505936bc237d481517a8622901878f",
|
||||
"integrations": "3c14d7de904911aad7f3ebec6d1a20b50843287f58125c526e167f429f3d455d",
|
||||
"login": "04f3ce79bfa5753f69e0d956c2a8999c0da549c7925634a3e8134975da0b0e0f",
|
||||
"sessions": "878dbe8f8ea783b44130c495814179519fa5c3aa2666ac87508f94d58dd008bf",
|
||||
"settings": "ea2505b9d0a6d6bb594dba87a92079de19baa6d494f0651693a7685489fb7de9",
|
||||
"unread": "745d9a1c70c7327aa0ae37328c2736ba6a5f6493db44ef7f12d4da241491b71f",
|
||||
"starred": "33dd40d1a24739e9d05f9cc4b66497cfdb8c86a7abb209a66ca65c2fbafc7d87",
|
||||
"unread": "d990b41e03912600f10069b33376c541a8ef518f302a60fd28763e97d44c85ba",
|
||||
"users": "44677e28bb5347799ed0020c90ec785aadec4b1454446d92411cfdaf6e32110b",
|
||||
}
|
||||
|
|
|
@ -373,6 +373,75 @@ func (c *Controller) ShowReadEntry(ctx *core.Context, request *core.Request, res
|
|||
}))
|
||||
}
|
||||
|
||||
// ShowStarredEntry shows a single feed entry in "starred" mode.
|
||||
func (c *Controller) ShowStarredEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.HTML().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithEntryID(entryID)
|
||||
builder.WithoutStatus(model.EntryStatusRemoved)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.HTML().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if entry.Status == model.EntryStatusUnread {
|
||||
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
|
||||
if err != nil {
|
||||
logger.Error("[Controller:ShowReadEntry] %v", err)
|
||||
response.HTML().ServerError(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStarred()
|
||||
|
||||
prevEntry, nextEntry, err := c.getEntryPrevNext(user, builder, entry.ID)
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
nextEntryRoute := ""
|
||||
if nextEntry != nil {
|
||||
nextEntryRoute = ctx.Route("starredEntry", "entryID", nextEntry.ID)
|
||||
}
|
||||
|
||||
prevEntryRoute := ""
|
||||
if prevEntry != nil {
|
||||
prevEntryRoute = ctx.Route("starredEntry", "entryID", prevEntry.ID)
|
||||
}
|
||||
|
||||
response.HTML().Render("entry", args.Merge(tplParams{
|
||||
"entry": entry,
|
||||
"prevEntry": prevEntry,
|
||||
"nextEntry": nextEntry,
|
||||
"nextEntryRoute": nextEntryRoute,
|
||||
"prevEntryRoute": prevEntryRoute,
|
||||
"menu": "starred",
|
||||
}))
|
||||
}
|
||||
|
||||
// UpdateEntriesStatus handles Ajax request to update the status for a list of entries.
|
||||
func (c *Controller) UpdateEntriesStatus(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
|
@ -412,7 +481,7 @@ func (c *Controller) getEntryPrevNext(user *model.User, builder *storage.EntryQu
|
|||
n := len(entries)
|
||||
for i := 0; i < n; i++ {
|
||||
if entries[i].ID == entryID {
|
||||
if i-1 > 0 {
|
||||
if i-1 >= 0 {
|
||||
prev = entries[i-1]
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/server/core"
|
||||
)
|
||||
|
||||
// ShowStarredPage renders the page with all starred entries.
|
||||
func (c *Controller) ShowStarredPage(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
offset := request.QueryIntegerParam("offset", 0)
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithoutStatus(model.EntryStatusRemoved)
|
||||
builder.WithStarred()
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(user.EntryDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
count, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.HTML().Render("starred", args.Merge(tplParams{
|
||||
"entries": entries,
|
||||
"total": count,
|
||||
"pagination": c.getPagination(ctx.Route("starred"), count, offset),
|
||||
"menu": "starred",
|
||||
}))
|
||||
}
|
||||
|
||||
// ToggleBookmark handles Ajax request to toggle bookmark value.
|
||||
func (c *Controller) ToggleBookmark(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.HTML().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.ToggleBookmark(user.ID, entryID); err != nil {
|
||||
logger.Error("[Controller:UpdateEntryStatus] %v", err)
|
||||
response.JSON().ServerError(nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard("OK")
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
alter table entries add column starred bool default 'f';
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-18 18:49:32.121198779 -0800 PST m=+0.002856573
|
||||
// 2017-12-22 11:25:01.937552528 -0800 PST m=+0.002520290
|
||||
|
||||
package sql
|
||||
|
||||
|
@ -122,6 +122,7 @@ alter table integrations add column wallabag_client_id text default '';
|
|||
alter table integrations add column wallabag_client_secret text default '';
|
||||
alter table integrations add column wallabag_username text default '';
|
||||
alter table integrations add column wallabag_password text default '';`,
|
||||
"schema_version_12": `alter table entries add column starred bool default 'f';`,
|
||||
"schema_version_2": `create extension if not exists hstore;
|
||||
alter table users add column extra hstore;
|
||||
create index users_extra_idx on users using gin(extra);
|
||||
|
@ -164,6 +165,7 @@ var SqlMapChecksums = map[string]string{
|
|||
"schema_version_1": "7be580fc8a93db5da54b2f6e87019803c33b0b0c28482c7af80cef873bdac4e2",
|
||||
"schema_version_10": "8faf15ddeff7c8cc305e66218face11ed92b97df2bdc2d0d7944d61441656795",
|
||||
"schema_version_11": "dc5bbc302e01e425b49c48ddcd8e29e3ab2bb8e73a6cd1858a6ba9fbec0b5243",
|
||||
"schema_version_12": "a95abab6cdf64811fc744abd37457e2928939d999c5ef00d2bdd9398e16f32fb",
|
||||
"schema_version_2": "e8e9ff32478df04fcddad10a34cba2e8bb1e67e7977b5bd6cdc4c31ec94282b4",
|
||||
"schema_version_3": "a54745dbc1c51c000f74d4e5068f1e2f43e83309f023415b1749a47d5c1e0f12",
|
||||
"schema_version_4": "216ea3a7d3e1704e40c797b5dc47456517c27dbb6ca98bf88812f4f63d74b5d9",
|
||||
|
|
|
@ -179,11 +179,24 @@ func (s *Storage) SetEntriesStatus(userID int64, entryIDs []int64, status string
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToggleBookmark toggles entry bookmark value.
|
||||
func (s *Storage) ToggleBookmark(userID int64, entryID int64) error {
|
||||
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:ToggleBookmark] userID=%d, entryID=%d", userID, entryID))
|
||||
|
||||
query := `UPDATE entries SET starred = NOT starred WHERE user_id=$1 AND id=$2`
|
||||
_, err := s.db.Exec(query, userID, entryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update toggle bookmark: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushHistory set all entries with the status "read" to "removed".
|
||||
func (s *Storage) FlushHistory(userID int64) error {
|
||||
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:FlushHistory] userID=%d", userID))
|
||||
|
||||
query := `UPDATE entries SET status=$1 WHERE user_id=$2 AND status=$3`
|
||||
query := `UPDATE entries SET status=$1 WHERE user_id=$2 AND status=$3 AND starred='f'`
|
||||
_, err := s.db.Exec(query, model.EntryStatusRemoved, userID, model.EntryStatusRead)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to flush history: %v", err)
|
||||
|
|
|
@ -32,6 +32,13 @@ type EntryQueryBuilder struct {
|
|||
greaterThanEntryID int64
|
||||
entryIDs []int64
|
||||
before *time.Time
|
||||
starred bool
|
||||
}
|
||||
|
||||
// WithStarred adds starred filter.
|
||||
func (e *EntryQueryBuilder) WithStarred() *EntryQueryBuilder {
|
||||
e.starred = true
|
||||
return e
|
||||
}
|
||||
|
||||
// Before add condition base on the entry date.
|
||||
|
@ -150,7 +157,8 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
|
|||
|
||||
query := `
|
||||
SELECT
|
||||
e.id, e.user_id, e.feed_id, e.hash, e.published_at at time zone '%s', e.title, e.url, e.author, e.content, e.status,
|
||||
e.id, e.user_id, e.feed_id, e.hash, e.published_at at time zone '%s', e.title,
|
||||
e.url, e.author, e.content, e.status, e.starred,
|
||||
f.title as feed_title, f.feed_url, f.site_url, f.checked_at,
|
||||
f.category_id, c.title as category_title, f.scraper_rules, f.rewrite_rules, f.crawler,
|
||||
fi.icon_id
|
||||
|
@ -191,6 +199,7 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
|
|||
&entry.Author,
|
||||
&entry.Content,
|
||||
&entry.Status,
|
||||
&entry.Starred,
|
||||
&entry.Feed.Title,
|
||||
&entry.Feed.FeedURL,
|
||||
&entry.Feed.SiteURL,
|
||||
|
@ -303,6 +312,10 @@ func (e *EntryQueryBuilder) buildCondition() ([]interface{}, string) {
|
|||
args = append(args, e.before)
|
||||
}
|
||||
|
||||
if e.starred {
|
||||
conditions = append(conditions, "e.starred is true")
|
||||
}
|
||||
|
||||
return args, strings.Join(conditions, " AND ")
|
||||
}
|
||||
|
||||
|
@ -334,5 +347,6 @@ func NewEntryQueryBuilder(store *Storage, userID int64, timezone string) *EntryQ
|
|||
store: store,
|
||||
userID: userID,
|
||||
timezone: timezone,
|
||||
starred: false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/miniflux/miniflux/sql"
|
||||
)
|
||||
|
||||
const schemaVersion = 11
|
||||
const schemaVersion = 12
|
||||
|
||||
// Migrate run database migrations.
|
||||
func (s *Storage) Migrate() {
|
||||
|
|
|
@ -291,8 +291,8 @@ func (c *Client) FeedIcon(feedID int64) (*FeedIcon, error) {
|
|||
return feedIcon, nil
|
||||
}
|
||||
|
||||
// Entry gets a single feed entry.
|
||||
func (c *Client) Entry(feedID, entryID int64) (*Entry, error) {
|
||||
// FeedEntry gets a single feed entry.
|
||||
func (c *Client) FeedEntry(feedID, entryID int64) (*Entry, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/entries/%d", feedID, entryID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -308,6 +308,23 @@ func (c *Client) Entry(feedID, entryID int64) (*Entry, error) {
|
|||
return entry, nil
|
||||
}
|
||||
|
||||
// Entry gets a single entry.
|
||||
func (c *Client) Entry(entryID int64) (*Entry, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d", entryID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var entry *Entry
|
||||
decoder := json.NewDecoder(body)
|
||||
if err := decoder.Decode(&entry); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// Entries fetch entries.
|
||||
func (c *Client) Entries(filter *Filter) (*EntryResultSet, error) {
|
||||
path := buildFilterQueryString("/v1/entries", filter)
|
||||
|
@ -362,6 +379,17 @@ func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToggleBookmark toggles entry bookmark value.
|
||||
func (c *Client) ToggleBookmark(entryID int64) error {
|
||||
body, err := c.request.Put(fmt.Sprintf("/v1/entries/%d/bookmark", entryID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewClient returns a new Client.
|
||||
func NewClient(endpoint, username, password string) *Client {
|
||||
return &Client{request: &request{endpoint: endpoint, username: username, password: password}}
|
||||
|
|
|
@ -101,6 +101,7 @@ type Entry struct {
|
|||
Date time.Time `json:"published_at"`
|
||||
Content string `json:"content"`
|
||||
Author string `json:"author"`
|
||||
Starred bool `json:"starred"`
|
||||
Enclosures Enclosures `json:"enclosures,omitempty"`
|
||||
Feed *Feed `json:"feed,omitempty"`
|
||||
Category *Category `json:"category,omitempty"`
|
||||
|
|
Loading…
Reference in New Issue