From da22d19fb45b5504f4191ad06d4e7637bfbc3916 Mon Sep 17 00:00:00 2001 From: r Date: Sun, 27 Sep 2020 09:29:17 +0000 Subject: [PATCH] Add bookmarks - Add bookmark/unbookmark link on mouse hover - Add bookmarks section on user profile page --- mastodon/accounts.go | 10 ++++++++++ mastodon/status.go | 23 ++++++++++++++++++++++ service/auth.go | 27 ++++++++++++++++++++++++-- service/logging.go | 19 +++++++++++++++++-- service/service.go | 41 ++++++++++++++++++++++++++++++++-------- service/transport.go | 44 ++++++++++++++++++++++++++++++++++++++++++- templates/status.tmpl | 13 +++++++++++++ templates/user.tmpl | 17 +++++++++++++++++ 8 files changed, 181 insertions(+), 13 deletions(-) diff --git a/mastodon/accounts.go b/mastodon/accounts.go index 7a44e2b..c5eb227 100644 --- a/mastodon/accounts.go +++ b/mastodon/accounts.go @@ -353,3 +353,13 @@ func (c *Client) UnSubscribe(ctx context.Context, id string) (*Relationship, err } return relationship, nil } + +// GetBookmarks returns the list of bookmarked statuses +func (c *Client) GetBookmarks(ctx context.Context, pg *Pagination) ([]*Status, error) { + var statuses []*Status + err := c.doAPI(ctx, http.MethodGet, "/api/v1/bookmarks", nil, &statuses, pg) + if err != nil { + return nil, err + } + return statuses, nil +} diff --git a/mastodon/status.go b/mastodon/status.go index 7a29b99..c8555d6 100644 --- a/mastodon/status.go +++ b/mastodon/status.go @@ -47,6 +47,7 @@ type Status struct { Application Application `json:"application"` Language string `json:"language"` Pinned interface{} `json:"pinned"` + Bookmarked bool `json:"bookmarked"` Poll *Poll `json:"poll"` // Custom fields @@ -366,3 +367,25 @@ func (c *Client) UnmuteConversation(ctx context.Context, id string) (*Status, er } return &status, nil } + +// Bookmark bookmarks status specified by id. +func (c *Client) Bookmark(ctx context.Context, id string) (*Status, error) { + var status Status + + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/bookmark", id), nil, &status, nil) + if err != nil { + return nil, err + } + return &status, nil +} + +// Unbookmark bookmarks status specified by id. +func (c *Client) Unbookmark(ctx context.Context, id string) (*Status, error) { + var status Status + + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%s/unbookmark", id), nil, &status, nil) + if err != nil { + return nil, err + } + return &status, nil +} diff --git a/service/auth.go b/service/auth.go index ef701c1..7845675 100644 --- a/service/auth.go +++ b/service/auth.go @@ -443,8 +443,7 @@ func (s *as) Delete(c *model.Client, id string) (err error) { return s.Service.Delete(c, id) } -func (s *as) ReadNotifications(c *model.Client, - maxID string) (err error) { +func (s *as) ReadNotifications(c *model.Client, maxID string) (err error) { err = s.authenticateClient(c) if err != nil { return @@ -455,3 +454,27 @@ func (s *as) ReadNotifications(c *model.Client, } return s.Service.ReadNotifications(c, maxID) } + +func (s *as) Bookmark(c *model.Client, id string) (err error) { + err = s.authenticateClient(c) + if err != nil { + return + } + err = checkCSRF(c) + if err != nil { + return + } + return s.Service.Bookmark(c, id) +} + +func (s *as) UnBookmark(c *model.Client, id string) (err error) { + err = s.authenticateClient(c) + if err != nil { + return + } + err = checkCSRF(c) + if err != nil { + return + } + return s.Service.UnBookmark(c, id) +} diff --git a/service/logging.go b/service/logging.go index 7df03de..3cb99bf 100644 --- a/service/logging.go +++ b/service/logging.go @@ -316,11 +316,26 @@ func (s *ls) Delete(c *model.Client, id string) (err error) { return s.Service.Delete(c, id) } -func (s *ls) ReadNotifications(c *model.Client, - maxID string) (err error) { +func (s *ls) ReadNotifications(c *model.Client, maxID string) (err error) { defer func(begin time.Time) { s.logger.Printf("method=%v, max_id=%v, took=%v, err=%v\n", "ReadNotifications", maxID, time.Since(begin), err) }(time.Now()) return s.Service.ReadNotifications(c, maxID) } + +func (s *ls) Bookmark(c *model.Client, id string) (err error) { + defer func(begin time.Time) { + s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", + "Bookmark", id, time.Since(begin), err) + }(time.Now()) + return s.Service.Bookmark(c, id) +} + +func (s *ls) UnBookmark(c *model.Client, id string) (err error) { + defer func(begin time.Time) { + s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", + "UnBookmark", id, time.Since(begin), err) + }(time.Now()) + return s.Service.UnBookmark(c, id) +} diff --git a/service/service.go b/service/service.go index c56d96a..de450b9 100644 --- a/service/service.go +++ b/service/service.go @@ -62,6 +62,8 @@ type Service interface { UnMuteConversation(c *model.Client, id string) (err error) Delete(c *model.Client, id string) (err error) ReadNotifications(c *model.Client, maxID string) (err error) + Bookmark(c *model.Client, id string) (err error) + UnBookmark(c *model.Client, id string) (err error) } type service struct { @@ -109,13 +111,13 @@ func getRendererContext(c *model.Client) *renderer.Context { settings = *model.NewSettings() } return &renderer.Context{ - HideAttachments: settings.HideAttachments, - MaskNSFW: settings.MaskNSFW, - ThreadInNewTab: settings.ThreadInNewTab, - FluorideMode: settings.FluorideMode, - DarkMode: settings.DarkMode, - CSRFToken: session.CSRFToken, - UserID: session.UserID, + HideAttachments: settings.HideAttachments, + MaskNSFW: settings.MaskNSFW, + ThreadInNewTab: settings.ThreadInNewTab, + FluorideMode: settings.FluorideMode, + DarkMode: settings.DarkMode, + CSRFToken: session.CSRFToken, + UserID: session.UserID, AntiDopamineMode: settings.AntiDopamineMode, } } @@ -464,6 +466,7 @@ func (svc *service) ServeUserPage(c *model.Client, id string, pageType string, if err != nil { return } + isCurrent := c.Session.UserID == user.ID switch pageType { case "": @@ -502,6 +505,18 @@ func (svc *service) ServeUserPage(c *model.Client, id string, pageType string, nextLink = fmt.Sprintf("/user/%s/media?max_id=%s", id, pg.MaxID) } + case "bookmarks": + if !isCurrent { + return errInvalidArgument + } + statuses, err = c.GetBookmarks(ctx, &pg) + if err != nil { + return + } + if len(statuses) == 20 && len(pg.MaxID) > 0 { + nextLink = fmt.Sprintf("/user/%s/bookmarks?max_id=%s", + id, pg.MaxID) + } default: return errInvalidArgument } @@ -509,7 +524,7 @@ func (svc *service) ServeUserPage(c *model.Client, id string, pageType string, commonData := svc.getCommonData(c, user.DisplayName) data := &renderer.UserData{ User: user, - IsCurrent: c.Session.UserID == user.ID, + IsCurrent: isCurrent, Type: pageType, Users: users, Statuses: statuses, @@ -890,3 +905,13 @@ func (svc *service) Delete(c *model.Client, id string) (err error) { func (svc *service) ReadNotifications(c *model.Client, maxID string) (err error) { return c.ReadNotifications(ctx, maxID) } + +func (svc *service) Bookmark(c *model.Client, id string) (err error) { + _, err = c.Bookmark(ctx, id) + return +} + +func (svc *service) UnBookmark(c *model.Client, id string) (err error) { + _, err = c.Unbookmark(ctx, id) + return +} diff --git a/service/transport.go b/service/transport.go index 7d27a84..4f73c5e 100644 --- a/service/transport.go +++ b/service/transport.go @@ -76,7 +76,7 @@ func NewHandler(s Service, staticDir string) http.Handler { c := newClient(w, req, "") err := s.ServeRootPage(c) if err != nil { - if (err == errInvalidAccessToken) { + if err == errInvalidAccessToken { w.Header().Add("Location", "/signin") w.WriteHeader(http.StatusFound) return @@ -676,6 +676,46 @@ func NewHandler(s Service, staticDir string) http.Handler { w.WriteHeader(http.StatusFound) } + bookmark := func(w http.ResponseWriter, req *http.Request) { + c := newClient(w, req, req.FormValue("csrf_token")) + id, _ := mux.Vars(req)["id"] + retweetedByID := req.FormValue("retweeted_by_id") + + err := s.Bookmark(c, id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + s.ServeErrorPage(c, err) + return + } + + rID := id + if len(retweetedByID) > 0 { + rID = retweetedByID + } + w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID) + w.WriteHeader(http.StatusFound) + } + + unBookmark := func(w http.ResponseWriter, req *http.Request) { + c := newClient(w, req, req.FormValue("csrf_token")) + id, _ := mux.Vars(req)["id"] + retweetedByID := req.FormValue("retweeted_by_id") + + err := s.UnBookmark(c, id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + s.ServeErrorPage(c, err) + return + } + + rID := id + if len(retweetedByID) > 0 { + rID = retweetedByID + } + w.Header().Add("Location", req.Header.Get("Referer")+"#status-"+rID) + w.WriteHeader(http.StatusFound) + } + signout := func(w http.ResponseWriter, req *http.Request) { c := newClient(w, req, req.FormValue("csrf_token")) @@ -791,6 +831,8 @@ func NewHandler(s Service, staticDir string) http.Handler { r.HandleFunc("/unmuteconv/{id}", unMuteConversation).Methods(http.MethodPost) r.HandleFunc("/delete/{id}", delete).Methods(http.MethodPost) r.HandleFunc("/notifications/read", readNotifications).Methods(http.MethodPost) + r.HandleFunc("/bookmark/{id}", bookmark).Methods(http.MethodPost) + r.HandleFunc("/unbookmark/{id}", unBookmark).Methods(http.MethodPost) r.HandleFunc("/signout", signout).Methods(http.MethodPost) r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost) r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost) diff --git a/templates/status.tmpl b/templates/status.tmpl index 1e3d514..6c255a0 100644 --- a/templates/status.tmpl +++ b/templates/status.tmpl @@ -46,6 +46,19 @@ {{end}} + {{if .Bookmarked}} +
+ + + +
+ {{else}} +
+ + + +
+ {{end}} {{if eq $.Ctx.UserID .Account.ID}}
diff --git a/templates/user.tmpl b/templates/user.tmpl index cb21b8a..3bb7523 100644 --- a/templates/user.tmpl +++ b/templates/user.tmpl @@ -97,6 +97,11 @@ followers ({{.User.FollowersCount}}) - media + {{if .IsCurrent}} +
+ bookmarks +
+ {{end}}
search statuses
@@ -111,6 +116,8 @@
Statuses
{{range .Statuses}} {{template "status.tmpl" (WithContext . $.Ctx)}} +{{else}} +
No data found
{{end}} {{else if eq .Type "following"}} @@ -125,6 +132,16 @@
Statuses with media
{{range .Statuses}} {{template "status.tmpl" (WithContext . $.Ctx)}} +{{else}} +
No data found
+{{end}} + +{{else if eq .Type "bookmarks"}} +
Bookmarks
+{{range .Statuses}} +{{template "status.tmpl" (WithContext . $.Ctx)}} +{{else}} +
No data found
{{end}} {{end}}