Merge pull request #539 from andreynering/notifications-step-2
Notifications - Step 2
This commit is contained in:
commit
79d527195d
10 changed files with 233 additions and 6 deletions
|
@ -167,6 +167,8 @@ func runWeb(ctx *cli.Context) error {
|
||||||
|
|
||||||
bindIgnErr := binding.BindIgnErr
|
bindIgnErr := binding.BindIgnErr
|
||||||
|
|
||||||
|
m.Use(user.GetNotificationCount)
|
||||||
|
|
||||||
// FIXME: not all routes need go through same middlewares.
|
// FIXME: not all routes need go through same middlewares.
|
||||||
// Especially some AJAX requests, we can reduce middleware number to improve performance.
|
// Especially some AJAX requests, we can reduce middleware number to improve performance.
|
||||||
// Routers.
|
// Routers.
|
||||||
|
@ -577,6 +579,8 @@ func runWeb(ctx *cli.Context) error {
|
||||||
})
|
})
|
||||||
// ***** END: Repository *****
|
// ***** END: Repository *****
|
||||||
|
|
||||||
|
m.Get("/notifications", reqSignIn, user.Notifications)
|
||||||
|
|
||||||
m.Group("/api", func() {
|
m.Group("/api", func() {
|
||||||
apiv1.RegisterRoutes(m)
|
apiv1.RegisterRoutes(m)
|
||||||
}, ignSignIn)
|
}, ignSignIn)
|
||||||
|
|
|
@ -182,14 +182,20 @@ func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotificationsForUser returns notifications for a given user and status
|
// NotificationsForUser returns notifications for a given user and status
|
||||||
func NotificationsForUser(user *User, status NotificationStatus) ([]*Notification, error) {
|
func NotificationsForUser(user *User, status NotificationStatus, page, perPage int) ([]*Notification, error) {
|
||||||
return notificationsForUser(x, user, status)
|
return notificationsForUser(x, user, status, page, perPage)
|
||||||
}
|
}
|
||||||
func notificationsForUser(e Engine, user *User, status NotificationStatus) (notifications []*Notification, err error) {
|
func notificationsForUser(e Engine, user *User, status NotificationStatus, page, perPage int) (notifications []*Notification, err error) {
|
||||||
err = e.
|
sess := e.
|
||||||
Where("user_id = ?", user.ID).
|
Where("user_id = ?", user.ID).
|
||||||
And("status = ?", status).
|
And("status = ?", status).
|
||||||
OrderBy("updated_unix DESC").
|
OrderBy("updated_unix DESC")
|
||||||
|
|
||||||
|
if page > 0 && perPage > 0 {
|
||||||
|
sess.Limit(perPage, (page-1)*perPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sess.
|
||||||
Find(¬ifications)
|
Find(¬ifications)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ version = Version
|
||||||
page = Page
|
page = Page
|
||||||
template = Template
|
template = Template
|
||||||
language = Language
|
language = Language
|
||||||
|
notifications = Notifications
|
||||||
create_new = Create...
|
create_new = Create...
|
||||||
user_profile_and_more = User profile and more
|
user_profile_and_more = User profile and more
|
||||||
signed_in_as = Signed in as
|
signed_in_as = Signed in as
|
||||||
|
@ -1232,3 +1233,10 @@ default_message = Drop files here or click to upload.
|
||||||
invalid_input_type = You can't upload files of this type.
|
invalid_input_type = You can't upload files of this type.
|
||||||
file_too_big = File size ({{filesize}} MB) exceeds maximum size ({{maxFilesize}} MB).
|
file_too_big = File size ({{filesize}} MB) exceeds maximum size ({{maxFilesize}} MB).
|
||||||
remove_file = Remove file
|
remove_file = Remove file
|
||||||
|
|
||||||
|
[notification]
|
||||||
|
notifications = Notifications
|
||||||
|
unread = Unread
|
||||||
|
read = Read
|
||||||
|
no_unread = You have no unread notifications.
|
||||||
|
no_read = You have no read notifications.
|
||||||
|
|
|
@ -13,6 +13,7 @@ version=Versão
|
||||||
page=Página
|
page=Página
|
||||||
template=Template
|
template=Template
|
||||||
language=Idioma
|
language=Idioma
|
||||||
|
notifications = Notificações
|
||||||
create_new=Criar...
|
create_new=Criar...
|
||||||
user_profile_and_more=Perfil do usuário e configurações
|
user_profile_and_more=Perfil do usuário e configurações
|
||||||
signed_in_as=Logado como
|
signed_in_as=Logado como
|
||||||
|
@ -1197,3 +1198,10 @@ default_message=Arraste e solte arquivos aqui, ou clique para selecioná-los.
|
||||||
invalid_input_type=Você não pode enviar arquivos deste tipo.
|
invalid_input_type=Você não pode enviar arquivos deste tipo.
|
||||||
file_too_big=O tamanho do arquivo ({{filesize}} MB) excede o limite máximo ({{maxFilesize}} MB).
|
file_too_big=O tamanho do arquivo ({{filesize}} MB) excede o limite máximo ({{maxFilesize}} MB).
|
||||||
remove_file=Remover
|
remove_file=Remover
|
||||||
|
|
||||||
|
[notification]
|
||||||
|
notifications = Notificações
|
||||||
|
unread = Não lidas
|
||||||
|
read = Lidas
|
||||||
|
no_unread = Você não possui notificações não lidas.
|
||||||
|
no_read = Você não possui notificações lidas.
|
||||||
|
|
|
@ -2704,6 +2704,24 @@ footer .ui.language .menu {
|
||||||
.user.followers .follow .ui.button {
|
.user.followers .follow .ui.button {
|
||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
}
|
}
|
||||||
|
.user.notification .octicon {
|
||||||
|
float: left;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
.user.notification .content {
|
||||||
|
float: left;
|
||||||
|
margin-left: 7px;
|
||||||
|
}
|
||||||
|
.user.notification .octicon-issue-opened,
|
||||||
|
.user.notification .octicon-git-pull-request {
|
||||||
|
color: #21ba45;
|
||||||
|
}
|
||||||
|
.user.notification .octicon-issue-closed {
|
||||||
|
color: #d01919;
|
||||||
|
}
|
||||||
|
.user.notification .octicon-git-merge {
|
||||||
|
color: #a333c8;
|
||||||
|
}
|
||||||
.dashboard {
|
.dashboard {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
padding-bottom: 80px;
|
padding-bottom: 80px;
|
||||||
|
|
|
@ -74,4 +74,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.notification {
|
||||||
|
.octicon {
|
||||||
|
float: left;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
float: left;
|
||||||
|
margin-left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octicon-issue-opened, .octicon-git-pull-request {
|
||||||
|
color: #21ba45;
|
||||||
|
}
|
||||||
|
.octicon-issue-closed {
|
||||||
|
color: #d01919;
|
||||||
|
}
|
||||||
|
.octicon-git-merge {
|
||||||
|
color: #a333c8;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
81
routers/user/notification.go
Normal file
81
routers/user/notification.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Unknwon/paginater"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tplNotification base.TplName = "user/notification/notification"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetNotificationCount is the middleware that sets the notification count in the context
|
||||||
|
func GetNotificationCount(c *context.Context) {
|
||||||
|
if strings.HasPrefix(c.Req.URL.Path, "/api") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.IsSigned {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := models.GetNotificationUnreadCount(c.User)
|
||||||
|
if err != nil {
|
||||||
|
c.Handle(500, "GetNotificationCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["NotificationUnreadCount"] = count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifications is the notifications page
|
||||||
|
func Notifications(c *context.Context) {
|
||||||
|
var (
|
||||||
|
keyword = c.Query("q")
|
||||||
|
status models.NotificationStatus
|
||||||
|
page = c.QueryInt("page")
|
||||||
|
perPage = c.QueryInt("perPage")
|
||||||
|
)
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if perPage < 1 {
|
||||||
|
perPage = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
switch keyword {
|
||||||
|
case "read":
|
||||||
|
status = models.NotificationStatusRead
|
||||||
|
default:
|
||||||
|
status = models.NotificationStatusUnread
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications, err := models.NotificationsForUser(c.User, status, page, perPage)
|
||||||
|
if err != nil {
|
||||||
|
c.Handle(500, "ErrNotificationsForUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := models.GetNotificationCount(c.User, status)
|
||||||
|
if err != nil {
|
||||||
|
c.Handle(500, "ErrGetNotificationCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
title := "Notifications"
|
||||||
|
if count := len(notifications); count > 0 {
|
||||||
|
title = fmt.Sprintf("(%d) %s", count, title)
|
||||||
|
}
|
||||||
|
c.Data["Title"] = title
|
||||||
|
c.Data["Keyword"] = keyword
|
||||||
|
c.Data["Status"] = status
|
||||||
|
c.Data["Notifications"] = notifications
|
||||||
|
c.Data["Page"] = paginater.New(int(total), perPage, page, 5)
|
||||||
|
c.HTML(200, tplNotification)
|
||||||
|
}
|
|
@ -29,7 +29,6 @@ const (
|
||||||
tplSettingsSocial base.TplName = "user/settings/social"
|
tplSettingsSocial base.TplName = "user/settings/social"
|
||||||
tplSettingsApplications base.TplName = "user/settings/applications"
|
tplSettingsApplications base.TplName = "user/settings/applications"
|
||||||
tplSettingsDelete base.TplName = "user/settings/delete"
|
tplSettingsDelete base.TplName = "user/settings/delete"
|
||||||
tplNotification base.TplName = "user/notification"
|
|
||||||
tplSecurity base.TplName = "user/security"
|
tplSecurity base.TplName = "user/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,18 @@
|
||||||
|
|
||||||
{{if .IsSigned}}
|
{{if .IsSigned}}
|
||||||
<div class="right menu">
|
<div class="right menu">
|
||||||
|
<a href="/notifications" class="ui head link jump item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
|
||||||
|
<span class="text">
|
||||||
|
<i class="octicon octicon-inbox"><span class="sr-only">{{.i18n.Tr "notifications"}}</span></i>
|
||||||
|
|
||||||
|
{{if .NotificationUnreadCount}}
|
||||||
|
<span class="ui red label">
|
||||||
|
{{.NotificationUnreadCount}}
|
||||||
|
</span>
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="ui dropdown head link jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted">
|
<div class="ui dropdown head link jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted">
|
||||||
<span class="text">
|
<span class="text">
|
||||||
<i class="octicon octicon-plus"><span class="sr-only">{{.i18n.Tr "create_new"}}</span></i>
|
<i class="octicon octicon-plus"><span class="sr-only">{{.i18n.Tr "create_new"}}</span></i>
|
||||||
|
|
70
templates/user/notification/notification.tmpl
Normal file
70
templates/user/notification/notification.tmpl
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
|
||||||
|
<div class="user notification">
|
||||||
|
<div class="ui container">
|
||||||
|
<h1 class="ui header">{{.i18n.Tr "notification.notifications"}}</h1>
|
||||||
|
|
||||||
|
<div class="ui top attached tabular menu">
|
||||||
|
<a href="/notifications?q=unread">
|
||||||
|
<div class="{{if eq .Status 1}}active{{end}} item">
|
||||||
|
{{.i18n.Tr "notification.unread"}}
|
||||||
|
{{if eq .Status 1}}
|
||||||
|
<div class="ui label">{{len .Notifications}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="/notifications?q=read">
|
||||||
|
<div class="{{if eq .Status 2}}active{{end}} item">
|
||||||
|
{{.i18n.Tr "notification.read"}}
|
||||||
|
{{if eq .Status 2}}
|
||||||
|
<div class="ui label">{{len .Notifications}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="ui bottom attached active tab segment">
|
||||||
|
{{if eq (len .Notifications) 0}}
|
||||||
|
{{if eq .Status 1}}
|
||||||
|
{{.i18n.Tr "notification.no_unread"}}
|
||||||
|
{{else}}
|
||||||
|
{{.i18n.Tr "notification.no_read"}}
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<div class="ui relaxed divided list">
|
||||||
|
{{range $notification := .Notifications}}
|
||||||
|
{{$issue := $notification.GetIssue}}
|
||||||
|
{{$repo := $notification.GetRepo}}
|
||||||
|
{{$repoOwner := $repo.MustOwner}}
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<a href="{{$.AppSubUrl}}/{{$repoOwner.Name}}/{{$repo.Name}}/issues/{{$issue.Index}}">
|
||||||
|
{{if and $issue.IsPull}}
|
||||||
|
{{if $issue.IsClosed}}
|
||||||
|
<i class="octicon octicon-git-merge"></i>
|
||||||
|
{{else}}
|
||||||
|
<i class="octicon octicon-git-pull-request"></i>
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
{{if $issue.IsClosed}}
|
||||||
|
<i class="octicon octicon-issue-closed"></i>
|
||||||
|
{{else}}
|
||||||
|
<i class="octicon octicon-issue-opened"></i>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">{{$repoOwner.Name}}/{{$repo.Name}}</div>
|
||||||
|
<div class="description">#{{$issue.Index}} - {{$issue.Title}}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "base/paginate" .}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "base/footer" .}}
|
Reference in a new issue