aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mastodon/notification.go24
-rw-r--r--renderer/model.go54
-rw-r--r--renderer/renderer.go5
-rw-r--r--service/auth.go8
-rw-r--r--service/logging.go8
-rw-r--r--service/service.go96
-rw-r--r--service/transport.go13
-rw-r--r--static/main.css24
-rw-r--r--templates/navigation.tmpl1
-rw-r--r--templates/notification.tmpl59
-rw-r--r--templates/status.tmpl4
-rw-r--r--templates/thread.tmpl2
-rw-r--r--templates/timeline.tmpl2
13 files changed, 271 insertions, 29 deletions
diff --git a/mastodon/notification.go b/mastodon/notification.go
index 236fcbf..d793905 100644
--- a/mastodon/notification.go
+++ b/mastodon/notification.go
@@ -4,16 +4,22 @@ import (
"context"
"fmt"
"net/http"
+ "net/url"
"time"
)
+type NotificationPleroma struct {
+ IsSeen bool `json:"is_seen"`
+}
+
// Notification hold information for mastodon notification.
type Notification struct {
- ID string `json:"id"`
- Type string `json:"type"`
- CreatedAt time.Time `json:"created_at"`
- Account Account `json:"account"`
- Status *Status `json:"status"`
+ ID string `json:"id"`
+ Type string `json:"type"`
+ CreatedAt time.Time `json:"created_at"`
+ Account Account `json:"account"`
+ Status *Status `json:"status"`
+ Pleroma *NotificationPleroma `json:"pleroma"`
}
// GetNotifications return notifications.
@@ -40,3 +46,11 @@ func (c *Client) GetNotification(ctx context.Context, id string) (*Notification,
func (c *Client) ClearNotifications(ctx context.Context) error {
return c.doAPI(ctx, http.MethodPost, "/api/v1/notifications/clear", nil, nil, nil)
}
+
+// ReadNotifications marks notifications as read
+// Currenly only works for Pleroma
+func (c *Client) ReadNotifications(ctx context.Context, maxID string) error {
+ params := url.Values{}
+ params.Set("max_id", maxID)
+ return c.doAPI(ctx, http.MethodPost, "/api/v1/pleroma/notifications/read", params, nil, nil)
+}
diff --git a/renderer/model.go b/renderer/model.go
index 6f6acc4..4529386 100644
--- a/renderer/model.go
+++ b/renderer/model.go
@@ -4,22 +4,34 @@ import (
"mastodon"
)
+type NavbarTemplateData struct {
+ NotificationCount int
+}
+
+func NewNavbarTemplateData(notificationCount int) *NavbarTemplateData {
+ return &NavbarTemplateData{
+ NotificationCount: notificationCount,
+ }
+}
+
type TimelinePageTemplateData struct {
- Statuses []*mastodon.Status
- HasNext bool
- NextLink string
- HasPrev bool
- PrevLink string
+ Statuses []*mastodon.Status
+ HasNext bool
+ NextLink string
+ HasPrev bool
+ PrevLink string
+ NavbarData *NavbarTemplateData
}
func NewTimelinePageTemplateData(statuses []*mastodon.Status, hasNext bool, nextLink string, hasPrev bool,
- prevLink string) *TimelinePageTemplateData {
+ prevLink string, navbarData *NavbarTemplateData) *TimelinePageTemplateData {
return &TimelinePageTemplateData{
- Statuses: statuses,
- HasNext: hasNext,
- NextLink: nextLink,
- HasPrev: hasPrev,
- PrevLink: prevLink,
+ Statuses: statuses,
+ HasNext: hasNext,
+ NextLink: nextLink,
+ HasPrev: hasPrev,
+ PrevLink: prevLink,
+ NavbarData: navbarData,
}
}
@@ -29,14 +41,32 @@ type ThreadPageTemplateData struct {
PostReply bool
ReplyToID string
ReplyContent string
+ NavbarData *NavbarTemplateData
}
-func NewThreadPageTemplateData(status *mastodon.Status, context *mastodon.Context, postReply bool, replyToID string, replyContent string) *ThreadPageTemplateData {
+func NewThreadPageTemplateData(status *mastodon.Status, context *mastodon.Context, postReply bool, replyToID string, replyContent string, navbarData *NavbarTemplateData) *ThreadPageTemplateData {
return &ThreadPageTemplateData{
Status: status,
Context: context,
PostReply: postReply,
ReplyToID: replyToID,
ReplyContent: replyContent,
+ NavbarData: navbarData,
+ }
+}
+
+type NotificationPageTemplateData struct {
+ Notifications []*mastodon.Notification
+ HasNext bool
+ NextLink string
+ NavbarData *NavbarTemplateData
+}
+
+func NewNotificationPageTemplateData(notifications []*mastodon.Notification, hasNext bool, nextLink string, navbarData *NavbarTemplateData) *NotificationPageTemplateData {
+ return &NotificationPageTemplateData{
+ Notifications: notifications,
+ HasNext: hasNext,
+ NextLink: nextLink,
+ NavbarData: navbarData,
}
}
diff --git a/renderer/renderer.go b/renderer/renderer.go
index c3d3526..394d74f 100644
--- a/renderer/renderer.go
+++ b/renderer/renderer.go
@@ -17,6 +17,7 @@ type Renderer interface {
RenderSigninPage(ctx context.Context, writer io.Writer) (err error)
RenderTimelinePage(ctx context.Context, writer io.Writer, data *TimelinePageTemplateData) (err error)
RenderThreadPage(ctx context.Context, writer io.Writer, data *ThreadPageTemplateData) (err error)
+ RenderNotificationPage(ctx context.Context, writer io.Writer, data *NotificationPageTemplateData) (err error)
}
type renderer struct {
@@ -60,6 +61,10 @@ func (r *renderer) RenderThreadPage(ctx context.Context, writer io.Writer, data
return r.template.ExecuteTemplate(writer, "thread.tmpl", data)
}
+func (r *renderer) RenderNotificationPage(ctx context.Context, writer io.Writer, data *NotificationPageTemplateData) (err error) {
+ return r.template.ExecuteTemplate(writer, "notification.tmpl", data)
+}
+
func WithEmojis(content string, emojis []mastodon.Emoji) string {
var emojiNameContentPair []string
for _, e := range emojis {
diff --git a/service/auth.go b/service/auth.go
index 98012af..e9bec38 100644
--- a/service/auth.go
+++ b/service/auth.go
@@ -111,6 +111,14 @@ func (s *authService) ServeThreadPage(ctx context.Context, client io.Writer, c *
return s.Service.ServeThreadPage(ctx, client, c, id, reply)
}
+func (s *authService) ServeNotificationPage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, minID string) (err error) {
+ c, err = s.getClient(ctx)
+ if err != nil {
+ return
+ }
+ return s.Service.ServeNotificationPage(ctx, client, c, maxID, minID)
+}
+
func (s *authService) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
c, err = s.getClient(ctx)
if err != nil {
diff --git a/service/logging.go b/service/logging.go
index 3a95a94..aa1da68 100644
--- a/service/logging.go
+++ b/service/logging.go
@@ -77,6 +77,14 @@ func (s *loggingService) ServeThreadPage(ctx context.Context, client io.Writer,
return s.Service.ServeThreadPage(ctx, client, c, id, reply)
}
+func (s *loggingService) ServeNotificationPage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, minID string) (err error) {
+ defer func(begin time.Time) {
+ s.logger.Printf("method=%v, max_id=%v, min_id=%v, took=%v, err=%v\n",
+ "ServeNotificationPage", maxID, minID, time.Since(begin), err)
+ }(time.Now())
+ return s.Service.ServeNotificationPage(ctx, client, c, maxID, minID)
+}
+
func (s *loggingService) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
defer func(begin time.Time) {
s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
diff --git a/service/service.go b/service/service.go
index e502b65..93f22fa 100644
--- a/service/service.go
+++ b/service/service.go
@@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"errors"
- "fmt"
"io"
"mime/multipart"
"net/http"
@@ -33,6 +32,7 @@ type Service interface {
ServeSigninPage(ctx context.Context, client io.Writer) (err error)
ServeTimelinePage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, sinceID string, minID string) (err error)
ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error)
+ ServeNotificationPage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, minID string) (err error)
Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
@@ -219,7 +219,7 @@ func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
if len(maxID) > 0 && len(statuses) > 0 {
hasPrev = true
- prevLink = fmt.Sprintf("/timeline?min_id=%s", statuses[0].ID)
+ prevLink = "/timeline?min_id=" + statuses[0].ID
}
if len(minID) > 0 && len(pg.MinID) > 0 {
newStatuses, err := c.GetTimelineHome(ctx, &mastodon.Pagination{MinID: pg.MinID, Limit: 20})
@@ -229,21 +229,26 @@ func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
newStatusesLen := len(newStatuses)
if newStatusesLen == 20 {
hasPrev = true
- prevLink = fmt.Sprintf("/timeline?min_id=%s", pg.MinID)
+ prevLink = "/timeline?min_id=" + pg.MinID
} else {
i := 20 - newStatusesLen - 1
if len(statuses) > i {
hasPrev = true
- prevLink = fmt.Sprintf("/timeline?min_id=%s", statuses[i].ID)
+ prevLink = "/timeline?min_id=" + statuses[i].ID
}
}
}
if len(pg.MaxID) > 0 {
hasNext = true
- nextLink = fmt.Sprintf("/timeline?max_id=%s", pg.MaxID)
+ nextLink = "/timeline?max_id=" + pg.MaxID
}
- data := renderer.NewTimelinePageTemplateData(statuses, hasNext, nextLink, hasPrev, prevLink)
+ navbarData, err := svc.getNavbarTemplateData(ctx, client, c)
+ if err != nil {
+ return
+ }
+
+ data := renderer.NewTimelinePageTemplateData(statuses, hasNext, nextLink, hasPrev, prevLink, navbarData)
err = svc.renderer.RenderTimelinePage(ctx, client, data)
if err != nil {
return
@@ -280,7 +285,12 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *ma
}
}
- data := renderer.NewThreadPageTemplateData(status, context, reply, id, content)
+ navbarData, err := svc.getNavbarTemplateData(ctx, client, c)
+ if err != nil {
+ return
+ }
+
+ data := renderer.NewThreadPageTemplateData(status, context, reply, id, content, navbarData)
err = svc.renderer.RenderThreadPage(ctx, client, data)
if err != nil {
return
@@ -289,6 +299,78 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *ma
return
}
+func (svc *service) ServeNotificationPage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, minID string) (err error) {
+ var hasNext bool
+ var nextLink string
+
+ var pg = mastodon.Pagination{
+ MaxID: maxID,
+ MinID: minID,
+ Limit: 20,
+ }
+
+ notifications, err := c.GetNotifications(ctx, &pg)
+ if err != nil {
+ return
+ }
+
+ var unreadCount int
+ for i := range notifications {
+ switch notifications[i].Type {
+ case "reblog", "favourite":
+ if notifications[i].Status != nil {
+ notifications[i].Status.Account.ID = ""
+ }
+ }
+ if notifications[i].Pleroma != nil && notifications[i].Pleroma.IsSeen {
+ unreadCount++
+ }
+ }
+
+ if unreadCount > 0 {
+ err := c.ReadNotifications(ctx, notifications[0].ID)
+ if err != nil {
+ return err
+ }
+ }
+
+ if len(pg.MaxID) > 0 {
+ hasNext = true
+ nextLink = "/notifications?max_id=" + pg.MaxID
+ }
+
+ navbarData, err := svc.getNavbarTemplateData(ctx, client, c)
+ if err != nil {
+ return
+ }
+
+ data := renderer.NewNotificationPageTemplateData(notifications, hasNext, nextLink, navbarData)
+ err = svc.renderer.RenderNotificationPage(ctx, client, data)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (svc *service) getNavbarTemplateData(ctx context.Context, client io.Writer, c *mastodon.Client) (data *renderer.NavbarTemplateData, err error) {
+ notifications, err := c.GetNotifications(ctx, nil)
+ if err != nil {
+ return
+ }
+
+ var notificationCount int
+ for i := range notifications {
+ if notifications[i].Pleroma != nil && !notifications[i].Pleroma.IsSeen {
+ notificationCount++
+ }
+ }
+
+ data = renderer.NewNavbarTemplateData(notificationCount)
+
+ return
+}
+
func (svc *service) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
_, err = c.Favourite(ctx, id)
return
diff --git a/service/transport.go b/service/transport.go
index d5a6ee8..377ab23 100644
--- a/service/transport.go
+++ b/service/transport.go
@@ -179,6 +179,19 @@ func NewHandler(s Service, staticDir string) http.Handler {
w.WriteHeader(http.StatusSeeOther)
}).Methods(http.MethodPost)
+ r.HandleFunc("/notifications", func(w http.ResponseWriter, req *http.Request) {
+ ctx := getContextWithSession(context.Background(), req)
+
+ maxID := req.URL.Query().Get("max_id")
+ minID := req.URL.Query().Get("min_id")
+
+ err := s.ServeNotificationPage(ctx, w, nil, maxID, minID)
+ if err != nil {
+ s.ServeErrorPage(ctx, w, err)
+ return
+ }
+ }).Methods(http.MethodGet)
+
r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
// TODO remove session from database
w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0"))
diff --git a/static/main.css b/static/main.css
index 3f551cc..8865820 100644
--- a/static/main.css
+++ b/static/main.css
@@ -17,11 +17,10 @@
.status-profile-img {
height: 48px;
width: 48px;
- object-fit: contain;
+ margin-right: 8px;
}
.status {
- margin: 0 8px;
}
.status a {
@@ -119,7 +118,6 @@
height: 24px;
width: 24px;
margin-bottom: -8px;
- object-fit: contain;
}
.retweet-info .status-dname{
@@ -143,3 +141,23 @@
.pagination a {
margin: 0 8px;
}
+
+.notification-container {
+ margin: 4px 0;
+ padding: 4px 4px;
+ border-left: 4px solid transparent;
+}
+
+.notification-container.unread {
+ border-color: #777777;
+}
+
+.notification-follow-container,
+.notification-like-container,
+.notification-retweet-container {
+ display: flex;
+}
+
+.notification-follow-uname {
+ margin-top: 8px;
+}
diff --git a/templates/navigation.tmpl b/templates/navigation.tmpl
index ea4a213..d86971c 100644
--- a/templates/navigation.tmpl
+++ b/templates/navigation.tmpl
@@ -1,4 +1,5 @@
<div class="navigation">
<a href="/timeline">home</a>
+ <a href="/notifications">notifications{{if gt .NotificationCount 0}} ({{.NotificationCount}}){{end}}</a>
<a href="/signout">sign out</a>
</div>
diff --git a/templates/notification.tmpl b/templates/notification.tmpl
new file mode 100644
index 0000000..099f17e
--- /dev/null
+++ b/templates/notification.tmpl
@@ -0,0 +1,59 @@
+{{template "header.tmpl"}}
+{{template "navigation.tmpl" .NavbarData}}
+<div class="page-title"> Notifications </div>
+
+{{range .Notifications}}
+<div class="notification-container {{if .Pleroma}}{{if not .Pleroma.IsSeen}}unread{{end}}{{end}}">
+ {{if eq .Type "follow"}}
+ <div class="notification-follow-container">
+ <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" />
+ <div>
+ <div>
+ <span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>
+ <span class="icon dripicons-user-group"></span>
+ followed you
+ </div>
+ <div class="notification-follow-uname">
+ @{{.Account.Acct}}
+ </div>
+ </div>
+ </div>
+
+ {{else if eq .Type "mention"}}
+ {{template "status" .Status}}
+
+ {{else if eq .Type "reblog"}}
+ <div class="notification-retweet-container">
+ <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" />
+ <div>
+ <div>
+ <span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>
+ <span class="icon dripicons-retweet retweeted"></span>
+ retweeted your post
+ </div>
+ {{template "status" .Status}}
+ </div>
+ </div>
+
+ {{else if eq .Type "favourite"}}
+ <div class="notification-like-container">
+ <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" />
+ <div>
+ <div>
+ <span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>
+ <span class="icon dripicons-star liked"></span>
+ liked your post
+ </div>
+ {{template "status" .Status}}
+ </div>
+ </div>
+ {{end}}
+</div>
+{{end}}
+
+<div class="pagination">
+ {{if .HasNext}}
+ <a href="{{.NextLink}}">next</a>
+ {{end}}
+</div>
+{{template "footer.tmpl"}}
diff --git a/templates/status.tmpl b/templates/status.tmpl
index 24f9a54..7020be0 100644
--- a/templates/status.tmpl
+++ b/templates/status.tmpl
@@ -11,13 +11,17 @@
{{block "status" .}}
<div class="status-container">
<div>
+ {{if ne .Account.ID ""}}
<img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" />
+ {{end}}
</div>
<div class="status">
+ {{if ne .Account.ID ""}}
<div class="status-name">
<span class="status-dname"> {{WithEmojis .Account.DisplayName .Account.Emojis}} </span>
<span class="status-uname"> {{.Account.Acct}} </span>
</div>
+ {{end}}
<div class="status-content"> {{WithEmojis .Content .Emojis}} </div>
<div class="status-media-container">
{{range .MediaAttachments}}
diff --git a/templates/thread.tmpl b/templates/thread.tmpl
index a3f7916..29d702b 100644
--- a/templates/thread.tmpl
+++ b/templates/thread.tmpl
@@ -1,6 +1,6 @@
{{template "header.tmpl"}}
+{{template "navigation.tmpl" .NavbarData}}
<div class="page-title"> Thread </div>
-{{template "navigation.tmpl"}}
{{range .Context.Ancestors}}
{{template "status.tmpl" .}}
diff --git a/templates/timeline.tmpl b/templates/timeline.tmpl
index 527c91b..53e3ad7 100644
--- a/templates/timeline.tmpl
+++ b/templates/timeline.tmpl
@@ -1,6 +1,6 @@
{{template "header.tmpl"}}
+{{template "navigation.tmpl" .NavbarData}}
<div class="page-title"> Timeline </div>
-{{template "navigation.tmpl"}}
<form class="timeline-post-form" action="/post" method="POST" enctype="multipart/form-data">
<label for="post-content"> New Post </label>