Add bookmarks
- Add bookmark/unbookmark link on mouse hover - Add bookmarks section on user profile page
This commit is contained in:
parent
0b8c41ca7c
commit
da22d19fb4
|
@ -353,3 +353,13 @@ func (c *Client) UnSubscribe(ctx context.Context, id string) (*Relationship, err
|
||||||
}
|
}
|
||||||
return relationship, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ type Status struct {
|
||||||
Application Application `json:"application"`
|
Application Application `json:"application"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Pinned interface{} `json:"pinned"`
|
Pinned interface{} `json:"pinned"`
|
||||||
|
Bookmarked bool `json:"bookmarked"`
|
||||||
Poll *Poll `json:"poll"`
|
Poll *Poll `json:"poll"`
|
||||||
|
|
||||||
// Custom fields
|
// Custom fields
|
||||||
|
@ -366,3 +367,25 @@ func (c *Client) UnmuteConversation(ctx context.Context, id string) (*Status, er
|
||||||
}
|
}
|
||||||
return &status, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -443,8 +443,7 @@ func (s *as) Delete(c *model.Client, id string) (err error) {
|
||||||
return s.Service.Delete(c, id)
|
return s.Service.Delete(c, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *as) ReadNotifications(c *model.Client,
|
func (s *as) ReadNotifications(c *model.Client, maxID string) (err error) {
|
||||||
maxID string) (err error) {
|
|
||||||
err = s.authenticateClient(c)
|
err = s.authenticateClient(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -455,3 +454,27 @@ func (s *as) ReadNotifications(c *model.Client,
|
||||||
}
|
}
|
||||||
return s.Service.ReadNotifications(c, maxID)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -316,11 +316,26 @@ func (s *ls) Delete(c *model.Client, id string) (err error) {
|
||||||
return s.Service.Delete(c, id)
|
return s.Service.Delete(c, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ls) ReadNotifications(c *model.Client,
|
func (s *ls) ReadNotifications(c *model.Client, maxID string) (err error) {
|
||||||
maxID string) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
s.logger.Printf("method=%v, max_id=%v, took=%v, err=%v\n",
|
s.logger.Printf("method=%v, max_id=%v, took=%v, err=%v\n",
|
||||||
"ReadNotifications", maxID, time.Since(begin), err)
|
"ReadNotifications", maxID, time.Since(begin), err)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
return s.Service.ReadNotifications(c, maxID)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -62,6 +62,8 @@ type Service interface {
|
||||||
UnMuteConversation(c *model.Client, id string) (err error)
|
UnMuteConversation(c *model.Client, id string) (err error)
|
||||||
Delete(c *model.Client, id string) (err error)
|
Delete(c *model.Client, id string) (err error)
|
||||||
ReadNotifications(c *model.Client, maxID 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 {
|
type service struct {
|
||||||
|
@ -109,13 +111,13 @@ func getRendererContext(c *model.Client) *renderer.Context {
|
||||||
settings = *model.NewSettings()
|
settings = *model.NewSettings()
|
||||||
}
|
}
|
||||||
return &renderer.Context{
|
return &renderer.Context{
|
||||||
HideAttachments: settings.HideAttachments,
|
HideAttachments: settings.HideAttachments,
|
||||||
MaskNSFW: settings.MaskNSFW,
|
MaskNSFW: settings.MaskNSFW,
|
||||||
ThreadInNewTab: settings.ThreadInNewTab,
|
ThreadInNewTab: settings.ThreadInNewTab,
|
||||||
FluorideMode: settings.FluorideMode,
|
FluorideMode: settings.FluorideMode,
|
||||||
DarkMode: settings.DarkMode,
|
DarkMode: settings.DarkMode,
|
||||||
CSRFToken: session.CSRFToken,
|
CSRFToken: session.CSRFToken,
|
||||||
UserID: session.UserID,
|
UserID: session.UserID,
|
||||||
AntiDopamineMode: settings.AntiDopamineMode,
|
AntiDopamineMode: settings.AntiDopamineMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,6 +466,7 @@ func (svc *service) ServeUserPage(c *model.Client, id string, pageType string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
isCurrent := c.Session.UserID == user.ID
|
||||||
|
|
||||||
switch pageType {
|
switch pageType {
|
||||||
case "":
|
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",
|
nextLink = fmt.Sprintf("/user/%s/media?max_id=%s",
|
||||||
id, pg.MaxID)
|
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:
|
default:
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
|
@ -509,7 +524,7 @@ func (svc *service) ServeUserPage(c *model.Client, id string, pageType string,
|
||||||
commonData := svc.getCommonData(c, user.DisplayName)
|
commonData := svc.getCommonData(c, user.DisplayName)
|
||||||
data := &renderer.UserData{
|
data := &renderer.UserData{
|
||||||
User: user,
|
User: user,
|
||||||
IsCurrent: c.Session.UserID == user.ID,
|
IsCurrent: isCurrent,
|
||||||
Type: pageType,
|
Type: pageType,
|
||||||
Users: users,
|
Users: users,
|
||||||
Statuses: statuses,
|
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) {
|
func (svc *service) ReadNotifications(c *model.Client, maxID string) (err error) {
|
||||||
return c.ReadNotifications(ctx, maxID)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ func NewHandler(s Service, staticDir string) http.Handler {
|
||||||
c := newClient(w, req, "")
|
c := newClient(w, req, "")
|
||||||
err := s.ServeRootPage(c)
|
err := s.ServeRootPage(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if (err == errInvalidAccessToken) {
|
if err == errInvalidAccessToken {
|
||||||
w.Header().Add("Location", "/signin")
|
w.Header().Add("Location", "/signin")
|
||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
return
|
return
|
||||||
|
@ -676,6 +676,46 @@ func NewHandler(s Service, staticDir string) http.Handler {
|
||||||
w.WriteHeader(http.StatusFound)
|
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) {
|
signout := func(w http.ResponseWriter, req *http.Request) {
|
||||||
c := newClient(w, req, req.FormValue("csrf_token"))
|
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("/unmuteconv/{id}", unMuteConversation).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/delete/{id}", delete).Methods(http.MethodPost)
|
r.HandleFunc("/delete/{id}", delete).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/notifications/read", readNotifications).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("/signout", signout).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost)
|
r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost)
|
r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost)
|
||||||
|
|
|
@ -46,6 +46,19 @@
|
||||||
<input type="submit" value="mute" class="btn-link more-link">
|
<input type="submit" value="mute" class="btn-link more-link">
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if .Bookmarked}}
|
||||||
|
<form action="/unbookmark/{{.ID}}" method="post" target="_self">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
|
||||||
|
<input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}">
|
||||||
|
<input type="submit" value="unbookmark" class="btn-link more-link">
|
||||||
|
</form>
|
||||||
|
{{else}}
|
||||||
|
<form action="/bookmark/{{.ID}}" method="post" target="_self">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
|
||||||
|
<input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}">
|
||||||
|
<input type="submit" value="bookmark" class="btn-link more-link">
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
{{if eq $.Ctx.UserID .Account.ID}}
|
{{if eq $.Ctx.UserID .Account.ID}}
|
||||||
<form action="/delete/{{.ID}}" method="post" target="_self">
|
<form action="/delete/{{.ID}}" method="post" target="_self">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
|
||||||
|
|
|
@ -97,6 +97,11 @@
|
||||||
<a href="/user/{{.User.ID}}/followers"> followers ({{.User.FollowersCount}}) </a> -
|
<a href="/user/{{.User.ID}}/followers"> followers ({{.User.FollowersCount}}) </a> -
|
||||||
<a href="/user/{{.User.ID}}/media"> media </a>
|
<a href="/user/{{.User.ID}}/media"> media </a>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .IsCurrent}}
|
||||||
|
<div>
|
||||||
|
<a href="/user/{{.User.ID}}/bookmarks"> bookmarks </a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
<div>
|
<div>
|
||||||
<a href="/usersearch/{{.User.ID}}"> search statuses </a>
|
<a href="/usersearch/{{.User.ID}}"> search statuses </a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,6 +116,8 @@
|
||||||
<div class="page-title"> Statuses </div>
|
<div class="page-title"> Statuses </div>
|
||||||
{{range .Statuses}}
|
{{range .Statuses}}
|
||||||
{{template "status.tmpl" (WithContext . $.Ctx)}}
|
{{template "status.tmpl" (WithContext . $.Ctx)}}
|
||||||
|
{{else}}
|
||||||
|
<div class="no-data-found">No data found</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{else if eq .Type "following"}}
|
{{else if eq .Type "following"}}
|
||||||
|
@ -125,6 +132,16 @@
|
||||||
<div class="page-title"> Statuses with media </div>
|
<div class="page-title"> Statuses with media </div>
|
||||||
{{range .Statuses}}
|
{{range .Statuses}}
|
||||||
{{template "status.tmpl" (WithContext . $.Ctx)}}
|
{{template "status.tmpl" (WithContext . $.Ctx)}}
|
||||||
|
{{else}}
|
||||||
|
<div class="no-data-found">No data found</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{else if eq .Type "bookmarks"}}
|
||||||
|
<div class="page-title"> Bookmarks </div>
|
||||||
|
{{range .Statuses}}
|
||||||
|
{{template "status.tmpl" (WithContext . $.Ctx)}}
|
||||||
|
{{else}}
|
||||||
|
<div class="no-data-found">No data found</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue