Skip to content

Commit 769e0a3

Browse files
andreyneringlunny
authored andcommitted
Notifications: mark as read/unread and pin (#629)
* Use relative URLs * Notifications - Mark as read/unread * Feature of pinning a notification * On view issue, do not mark as read a pinned notification
1 parent cbf2a96 commit 769e0a3

File tree

8 files changed

+168
-31
lines changed

8 files changed

+168
-31
lines changed

cmd/web.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,10 @@ func runWeb(ctx *cli.Context) error {
589589
})
590590
// ***** END: Repository *****
591591

592-
m.Get("/notifications", reqSignIn, user.Notifications)
592+
m.Group("/notifications", func() {
593+
m.Get("", user.Notifications)
594+
m.Post("/status", user.NotificationStatusPost)
595+
}, reqSignIn)
593596

594597
m.Group("/api", func() {
595598
apiv1.RegisterRoutes(m)

models/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ func (issue *Issue) ReadBy(userID int64) error {
448448
return err
449449
}
450450

451-
if err := setNotificationStatusRead(x, userID, issue.ID); err != nil {
451+
if err := setNotificationStatusReadIfUnread(x, userID, issue.ID); err != nil {
452452
return err
453453
}
454454

models/notification.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package models
66

77
import (
8+
"fmt"
89
"time"
910
)
1011

@@ -20,6 +21,8 @@ const (
2021
NotificationStatusUnread NotificationStatus = iota + 1
2122
// NotificationStatusRead represents a read notification
2223
NotificationStatusRead
24+
// NotificationStatusPinned represents a pinned notification
25+
NotificationStatusPinned
2326
)
2427

2528
const (
@@ -182,13 +185,19 @@ func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error
182185
}
183186

184187
// NotificationsForUser returns notifications for a given user and status
185-
func NotificationsForUser(user *User, status NotificationStatus, page, perPage int) ([]*Notification, error) {
186-
return notificationsForUser(x, user, status, page, perPage)
188+
func NotificationsForUser(user *User, statuses []NotificationStatus, page, perPage int) ([]*Notification, error) {
189+
return notificationsForUser(x, user, statuses, page, perPage)
187190
}
188-
func notificationsForUser(e Engine, user *User, status NotificationStatus, page, perPage int) (notifications []*Notification, err error) {
191+
func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, page, perPage int) (notifications []*Notification, err error) {
192+
// FIXME: Xorm does not support aliases types (like NotificationStatus) on In() method
193+
s := make([]uint8, len(statuses))
194+
for i, status := range statuses {
195+
s[i] = uint8(status)
196+
}
197+
189198
sess := e.
190199
Where("user_id = ?", user.ID).
191-
And("status = ?", status).
200+
In("status", s).
192201
OrderBy("updated_unix DESC")
193202

194203
if page > 0 && perPage > 0 {
@@ -241,15 +250,53 @@ func getNotificationCount(e Engine, user *User, status NotificationStatus) (coun
241250
return
242251
}
243252

244-
func setNotificationStatusRead(e Engine, userID, issueID int64) error {
253+
func setNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
245254
notification, err := getIssueNotification(e, userID, issueID)
246255
// ignore if not exists
247256
if err != nil {
248257
return nil
249258
}
250259

260+
if notification.Status != NotificationStatusUnread {
261+
return nil
262+
}
263+
251264
notification.Status = NotificationStatusRead
252265

253266
_, err = e.Id(notification.ID).Update(notification)
254267
return err
255268
}
269+
270+
// SetNotificationStatus change the notification status
271+
func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) error {
272+
notification, err := getNotificationByID(notificationID)
273+
if err != nil {
274+
return err
275+
}
276+
277+
if notification.UserID != user.ID {
278+
return fmt.Errorf("Can't change notification of another user: %d, %d", notification.UserID, user.ID)
279+
}
280+
281+
notification.Status = status
282+
283+
_, err = x.Id(notificationID).Update(notification)
284+
return err
285+
}
286+
287+
func getNotificationByID(notificationID int64) (*Notification, error) {
288+
notification := new(Notification)
289+
ok, err := x.
290+
Where("id = ?", notificationID).
291+
Get(notification)
292+
293+
if err != nil {
294+
return nil, err
295+
}
296+
297+
if !ok {
298+
return nil, fmt.Errorf("Notification %d does not exists", notificationID)
299+
}
300+
301+
return notification, nil
302+
}

public/css/index.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,6 +2712,12 @@ footer .ui.language .menu {
27122712
float: left;
27132713
margin-left: 7px;
27142714
}
2715+
.user.notification .buttons-panel button {
2716+
padding: 3px;
2717+
}
2718+
.user.notification .buttons-panel form {
2719+
display: inline-block;
2720+
}
27152721
.user.notification .octicon-issue-opened,
27162722
.user.notification .octicon-git-pull-request {
27172723
color: #21ba45;
@@ -2722,6 +2728,9 @@ footer .ui.language .menu {
27222728
.user.notification .octicon-git-merge {
27232729
color: #a333c8;
27242730
}
2731+
.user.notification .octicon-pin {
2732+
color: #2185d0;
2733+
}
27252734
.dashboard {
27262735
padding-top: 15px;
27272736
padding-bottom: 80px;

public/less/_user.less

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@
8585
margin-left: 7px;
8686
}
8787

88+
.buttons-panel {
89+
button {
90+
padding: 3px;
91+
}
92+
93+
form {
94+
display: inline-block;
95+
}
96+
}
97+
8898
.octicon-issue-opened, .octicon-git-pull-request {
8999
color: #21ba45;
90100
}
@@ -94,5 +104,8 @@
94104
.octicon-git-merge {
95105
color: #a333c8;
96106
}
107+
.octicon-pin {
108+
color: #2185d0;
109+
}
97110
}
98111
}

routers/user/notification.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package user
22

33
import (
4+
"errors"
45
"fmt"
6+
"strconv"
57
"strings"
68

79
"github.com/Unknwon/paginater"
810

911
"code.gitea.io/gitea/models"
1012
"code.gitea.io/gitea/modules/base"
1113
"code.gitea.io/gitea/modules/context"
14+
"code.gitea.io/gitea/modules/setting"
1215
)
1316

1417
const (
@@ -56,7 +59,8 @@ func Notifications(c *context.Context) {
5659
status = models.NotificationStatusUnread
5760
}
5861

59-
notifications, err := models.NotificationsForUser(c.User, status, page, perPage)
62+
statuses := []models.NotificationStatus{status, models.NotificationStatusPinned}
63+
notifications, err := models.NotificationsForUser(c.User, statuses, page, perPage)
6064
if err != nil {
6165
c.Handle(500, "ErrNotificationsForUser", err)
6266
return
@@ -79,3 +83,32 @@ func Notifications(c *context.Context) {
7983
c.Data["Page"] = paginater.New(int(total), perPage, page, 5)
8084
c.HTML(200, tplNotification)
8185
}
86+
87+
// NotificationStatusPost is a route for changing the status of a notification
88+
func NotificationStatusPost(c *context.Context) {
89+
var (
90+
notificationID, _ = strconv.ParseInt(c.Req.PostFormValue("notification_id"), 10, 64)
91+
statusStr = c.Req.PostFormValue("status")
92+
status models.NotificationStatus
93+
)
94+
95+
switch statusStr {
96+
case "read":
97+
status = models.NotificationStatusRead
98+
case "unread":
99+
status = models.NotificationStatusUnread
100+
case "pinned":
101+
status = models.NotificationStatusPinned
102+
default:
103+
c.Handle(500, "InvalidNotificationStatus", errors.New("Invalid notification status"))
104+
return
105+
}
106+
107+
if err := models.SetNotificationStatus(notificationID, c.User, status); err != nil {
108+
c.Handle(500, "SetNotificationStatus", err)
109+
return
110+
}
111+
112+
url := fmt.Sprintf("%s/notifications", setting.AppSubURL)
113+
c.Redirect(url, 303)
114+
}

templates/base/head.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282

8383
{{if .IsSigned}}
8484
<div class="right menu">
85-
<a href="/notifications" class="ui head link jump item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
85+
<a href="{{$.AppSubUrl}}/notifications" class="ui head link jump item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
8686
<span class="text">
8787
<i class="octicon octicon-inbox"><span class="sr-only">{{.i18n.Tr "notifications"}}</span></i>
8888

templates/user/notification/notification.tmpl

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
<h1 class="ui header">{{.i18n.Tr "notification.notifications"}}</h1>
66

77
<div class="ui top attached tabular menu">
8-
<a href="/notifications?q=unread">
8+
<a href="{{$.AppSubUrl}}/notifications?q=unread">
99
<div class="{{if eq .Status 1}}active{{end}} item">
1010
{{.i18n.Tr "notification.unread"}}
1111
{{if eq .Status 1}}
1212
<div class="ui label">{{len .Notifications}}</div>
1313
{{end}}
1414
</div>
1515
</a>
16-
<a href="/notifications?q=read">
16+
<a href="{{$.AppSubUrl}}/notifications?q=read">
1717
<div class="{{if eq .Status 2}}active{{end}} item">
1818
{{.i18n.Tr "notification.read"}}
1919
{{if eq .Status 2}}
@@ -30,34 +30,66 @@
3030
{{.i18n.Tr "notification.no_read"}}
3131
{{end}}
3232
{{else}}
33-
<div class="ui relaxed divided list">
33+
<div class="ui relaxed divided selection list">
3434
{{range $notification := .Notifications}}
3535
{{$issue := $notification.GetIssue}}
3636
{{$repo := $notification.GetRepo}}
3737
{{$repoOwner := $repo.MustOwner}}
3838

39-
<div class="item">
40-
<a href="{{$.AppSubUrl}}/{{$repoOwner.Name}}/{{$repo.Name}}/issues/{{$issue.Index}}">
41-
{{if and $issue.IsPull}}
42-
{{if $issue.IsClosed}}
43-
<i class="octicon octicon-git-merge"></i>
44-
{{else}}
45-
<i class="octicon octicon-git-pull-request"></i>
46-
{{end}}
39+
<a class="item" href="{{$.AppSubUrl}}/{{$repoOwner.Name}}/{{$repo.Name}}/issues/{{$issue.Index}}">
40+
<div class="buttons-panel right floated content">
41+
{{if ne $notification.Status 3}}
42+
<form action="{{$.AppSubUrl}}/notifications/status" method="POST">
43+
{{$.CsrfTokenHtml}}
44+
<input type="hidden" name="notification_id" value="{{$notification.ID}}" />
45+
<input type="hidden" name="status" value="pinned" />
46+
<button class="ui button" title="Pin notification">
47+
<i class="octicon octicon-pin"></i>
48+
</button>
49+
</form>
50+
{{end}}
51+
{{if or (eq $notification.Status 1) (eq $notification.Status 3)}}
52+
<form action="{{$.AppSubUrl}}/notifications/status" method="POST">
53+
{{$.CsrfTokenHtml}}
54+
<input type="hidden" name="notification_id" value="{{$notification.ID}}" />
55+
<input type="hidden" name="status" value="read" />
56+
<button class="ui button" title="Mark as read">
57+
<i class="octicon octicon-check"></i>
58+
</button>
59+
</form>
60+
{{else if eq $notification.Status 2}}
61+
<form action="{{$.AppSubUrl}}/notifications/status" method="POST">
62+
{{$.CsrfTokenHtml}}
63+
<input type="hidden" name="notification_id" value="{{$notification.ID}}" />
64+
<input type="hidden" name="status" value="unread" />
65+
<button class="ui button" title="Mark as unread">
66+
<i class="octicon octicon-bell"></i>
67+
</button>
68+
</form>
69+
{{end}}
70+
</div>
71+
72+
{{if eq $notification.Status 3}}
73+
<i class="blue octicon octicon-pin"></i>
74+
{{else if $issue.IsPull}}
75+
{{if $issue.IsClosed}}
76+
<i class="octicon octicon-git-merge"></i>
77+
{{else}}
78+
<i class="octicon octicon-git-pull-request"></i>
79+
{{end}}
80+
{{else}}
81+
{{if $issue.IsClosed}}
82+
<i class="octicon octicon-issue-closed"></i>
4783
{{else}}
48-
{{if $issue.IsClosed}}
49-
<i class="octicon octicon-issue-closed"></i>
50-
{{else}}
51-
<i class="octicon octicon-issue-opened"></i>
52-
{{end}}
84+
<i class="octicon octicon-issue-opened"></i>
5385
{{end}}
86+
{{end}}
5487

55-
<div class="content">
56-
<div class="header">{{$repoOwner.Name}}/{{$repo.Name}}</div>
57-
<div class="description">#{{$issue.Index}} - {{$issue.Title}}</div>
58-
</div>
59-
</a>
60-
</div>
88+
<div class="content">
89+
<div class="header">{{$repoOwner.Name}}/{{$repo.Name}}</div>
90+
<div class="description">#{{$issue.Index}} - {{$issue.Title}}</div>
91+
</div>
92+
</a>
6193
{{end}}
6294
</div>
6395
{{end}}

0 commit comments

Comments
 (0)