| @@ -168,6 +168,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. | ||||
| @@ -578,6 +580,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) | ||||
| @@ -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 | ||||
| @@ -1230,3 +1231,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. | |||||
| @@ -2694,6 +2694,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: green; | |||||
| } | |||||
| .user.notification .octicon-issue-closed { | |||||
| color: red; | |||||
| } | |||||
| .user.notification .octicon-git-merge { | |||||
| color: purple; | |||||
| } | |||||
| .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: green; | |||||
| } | |||||
| .octicon-issue-closed { | |||||
| color: red; | |||||
| } | |||||
| .octicon-git-merge { | |||||
| color: purple; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,54 @@ | |||||
| package user | |||||
| import ( | |||||
| "fmt" | |||||
| "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 !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 status models.NotificationStatus | |||||
| switch c.Query("status") { | |||||
| case "read": | |||||
| status = models.NotificationStatusRead | |||||
| default: | |||||
| status = models.NotificationStatusUnread | |||||
| } | |||||
| notifications, err := models.NotificationsForUser(c.User, status) | |||||
| if err != nil { | |||||
| c.Handle(500, "ErrNotificationsForUser", err) | |||||
| return | |||||
| } | |||||
| title := "Notifications" | |||||
| if count := len(notifications); count > 0 { | |||||
| title = fmt.Sprintf("(%d) %s", count, title) | |||||
| } | |||||
| c.Data["Title"] = title | |||||
| c.Data["Status"] = status | |||||
| c.Data["Notifications"] = notifications | |||||
| 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> | ||||
| @@ -0,0 +1,68 @@ | |||||
| {{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?status=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?status=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.Title}}</div> | |||||
| </div> | |||||
| </a> | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||