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 | ||||
| 
 | ||||
| 	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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										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" | ||||
| 	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> | ||||
|  |  | |||
							
								
								
									
										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" .}} | ||||
		Loading…
	
		Reference in a new issue