Notifications - Step 2tags/v1.21.12.1
| @@ -167,6 +167,8 @@ func runWeb(ctx *cli.Context) error { | |||
| bindIgnErr := binding.BindIgnErr | |||
| m.Use(user.GetNotificationCount) | |||
| // FIXME: not all routes need go through same middlewares. | |||
| // Especially some AJAX requests, we can reduce middleware number to improve performance. | |||
| // Routers. | |||
| @@ -577,6 +579,8 @@ func runWeb(ctx *cli.Context) error { | |||
| }) | |||
| // ***** END: Repository ***** | |||
| m.Get("/notifications", reqSignIn, user.Notifications) | |||
| m.Group("/api", func() { | |||
| apiv1.RegisterRoutes(m) | |||
| }, ignSignIn) | |||
| @@ -182,14 +182,20 @@ func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error | |||
| } | |||
| // NotificationsForUser returns notifications for a given user and status | |||
| func NotificationsForUser(user *User, status NotificationStatus) ([]*Notification, error) { | |||
| return notificationsForUser(x, user, status) | |||
| func NotificationsForUser(user *User, status NotificationStatus, page, perPage int) ([]*Notification, error) { | |||
| return notificationsForUser(x, user, status, page, perPage) | |||
| } | |||
| func notificationsForUser(e Engine, user *User, status NotificationStatus) (notifications []*Notification, err error) { | |||
| err = e. | |||
| func notificationsForUser(e Engine, user *User, status NotificationStatus, page, perPage int) (notifications []*Notification, err error) { | |||
| sess := e. | |||
| Where("user_id = ?", user.ID). | |||
| 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) | |||
| return | |||
| } | |||
| @@ -13,6 +13,7 @@ version = Version | |||
| page = Page | |||
| template = Template | |||
| language = Language | |||
| notifications = Notifications | |||
| create_new = Create... | |||
| user_profile_and_more = User profile and more | |||
| 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. | |||
| file_too_big = File size ({{filesize}} MB) exceeds maximum size ({{maxFilesize}} MB). | |||
| 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 | |||
| template=Template | |||
| language=Idioma | |||
| notifications = Notificações | |||
| create_new=Criar... | |||
| user_profile_and_more=Perfil do usuário e configurações | |||
| 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. | |||
| file_too_big=O tamanho do arquivo ({{filesize}} MB) excede o limite máximo ({{maxFilesize}} MB). | |||
| 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 { | |||
| 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 { | |||
| padding-top: 15px; | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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" | |||
| tplSettingsApplications base.TplName = "user/settings/applications" | |||
| tplSettingsDelete base.TplName = "user/settings/delete" | |||
| tplNotification base.TplName = "user/notification" | |||
| tplSecurity base.TplName = "user/security" | |||
| ) | |||
| @@ -82,6 +82,18 @@ | |||
| {{if .IsSigned}} | |||
| <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"> | |||
| <span class="text"> | |||
| <i class="octicon octicon-plus"><span class="sr-only">{{.i18n.Tr "create_new"}}</span></i> | |||
| @@ -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" .}} | |||