mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:45:25 +01:00 
			
		
		
		
	#1692 APIs: Users Followers
- User profile un/follow - List user's followers/following
This commit is contained in:
		
							parent
							
								
									c62a6b7a12
								
							
						
					
					
						commit
						a49af93faf
					
				| @ -13,7 +13,7 @@ watch_dirs = [ | ||||
| watch_exts = [".go"] | ||||
| build_delay = 1500 | ||||
| cmds = [ | ||||
| 	["go", "install", "-race"], # sqlite redis memcache cert pam tidb | ||||
| 	["go", "install", "-v", "-race"], # sqlite redis memcache cert pam tidb | ||||
| 	["go", "build", "-race"], | ||||
| 	["./gogs", "web"] | ||||
| ] | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -16,7 +16,7 @@ NOW = $(shell date -u '+%Y%m%d%I%M%S') | ||||
| .IGNORE: public/css/gogs.css | ||||
| 
 | ||||
| build: $(GENERATED) | ||||
| 	go install -ldflags '$(LDFLAGS)' -tags '$(TAGS)' | ||||
| 	go install -v -ldflags '$(LDFLAGS)' -tags '$(TAGS)' | ||||
| 	cp '$(GOPATH)/bin/gogs' . | ||||
| 
 | ||||
| govet: | ||||
|  | ||||
| @ -3,7 +3,7 @@ Gogs - Go Git Service [ | ||||
| 
 | ||||
| ##### Current version: 0.8.12 | ||||
| ##### Current version: 0.8.13 | ||||
| 
 | ||||
| | Web | UI  | Preview  | | ||||
| |:-------------:|:-------:|:-------:| | ||||
| @ -82,6 +82,7 @@ There are 5 ways to install Gogs: | ||||
| - [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese) | ||||
| - [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html) | ||||
| - [Gogs on Raspberry Pi](http://blog.meinside.pe.kr/Gogs-on-Raspberry-Pi/) | ||||
| - [Cloudflare Full SSL with GOGS (Go Git Service) using NGINX](http://www.listekconsulting.com/articles/cloudflare-full-ssl-with-gogs-go-git-service-using-nginx/) | ||||
| 
 | ||||
| ### Screencasts | ||||
| 
 | ||||
| @ -101,6 +102,7 @@ There are 5 ways to install Gogs: | ||||
| - [Drone](https://github.com/drone/drone) (CI) | ||||
| - [Fabric8](http://fabric8.io/) (DevOps) | ||||
| - [Taiga](https://taiga.io/) (Project Management) | ||||
| - [Puppet](https://forge.puppetlabs.com/Siteminds/gogs) (IT) | ||||
| 
 | ||||
| ### Product Support | ||||
| 
 | ||||
|  | ||||
| @ -73,6 +73,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自 | ||||
| - [Drone](https://github.com/drone/drone)(CI) | ||||
| - [Fabric8](http://fabric8.io/)(DevOps) | ||||
| - [Taiga](https://taiga.io/)(项目管理) | ||||
| - [Puppet](https://forge.puppetlabs.com/Siteminds/gogs)(IT) | ||||
| 
 | ||||
| ### 产品支持 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										12
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								cmd/web.go
									
									
									
									
									
								
							| @ -289,7 +289,13 @@ func runWeb(ctx *cli.Context) { | ||||
| 	// ***** END: Admin ***** | ||||
| 
 | ||||
| 	m.Group("", func() { | ||||
| 		m.Get("/:username", user.Profile) | ||||
| 		m.Group("/:username", func() { | ||||
| 			m.Get("", user.Profile) | ||||
| 			m.Get("/followers", user.Followers) | ||||
| 			m.Get("/following", user.Following) | ||||
| 			m.Get("/stars", user.Stars) | ||||
| 		}) | ||||
| 
 | ||||
| 		m.Get("/attachments/:uuid", func(ctx *middleware.Context) { | ||||
| 			attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid")) | ||||
| 			if err != nil { | ||||
| @ -319,6 +325,10 @@ func runWeb(ctx *cli.Context) { | ||||
| 		m.Post("/issues/attachments", repo.UploadIssueAttachment) | ||||
| 	}, ignSignIn) | ||||
| 
 | ||||
| 	m.Group("/:username", func() { | ||||
| 		m.Get("/action/:action", user.Action) | ||||
| 	}, reqSignIn) | ||||
| 
 | ||||
| 	if macaron.Env == macaron.DEV { | ||||
| 		m.Get("/template/*", dev.TemplatePreview) | ||||
| 	} | ||||
|  | ||||
| @ -230,8 +230,10 @@ join_on = Joined on | ||||
| repositories = Repositories | ||||
| activity = Public Activity | ||||
| followers = Followers | ||||
| starred = Starred | ||||
| starred = Starred repositories | ||||
| following = Following | ||||
| follow = Follow | ||||
| unfollow = Unfollow | ||||
| 
 | ||||
| form.name_reserved = Username '%s' is reserved. | ||||
| form.name_pattern_not_allowed = Username pattern '%s' is not allowed. | ||||
|  | ||||
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							| @ -17,7 +17,7 @@ import ( | ||||
| 	"github.com/gogits/gogs/modules/setting" | ||||
| ) | ||||
| 
 | ||||
| const APP_VER = "0.8.12.1219" | ||||
| const APP_VER = "0.8.13.1221" | ||||
| 
 | ||||
| func init() { | ||||
| 	runtime.GOMAXPROCS(runtime.NumCPU()) | ||||
|  | ||||
| @ -665,6 +665,47 @@ func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int | ||||
| 	return ius, err | ||||
| } | ||||
| 
 | ||||
| func UpdateMentions(userNames []string, issueId int64) error { | ||||
| 	for i := range userNames { | ||||
| 		userNames[i] = strings.ToLower(userNames[i]) | ||||
| 	} | ||||
| 	users := make([]*User, 0, len(userNames)) | ||||
| 
 | ||||
| 	if err := x.Where("lower_name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("lower_name ASC").Find(&users); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	ids := make([]int64, 0, len(userNames)) | ||||
| 	for _, user := range users { | ||||
| 		ids = append(ids, user.Id) | ||||
| 		if !user.IsOrganization() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if user.NumMembers == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		tempIds := make([]int64, 0, user.NumMembers) | ||||
| 		orgUsers, err := GetOrgUsersByOrgId(user.Id) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		for _, orgUser := range orgUsers { | ||||
| 			tempIds = append(tempIds, orgUser.ID) | ||||
| 		} | ||||
| 
 | ||||
| 		ids = append(ids, tempIds...) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := UpdateIssueUsersByMentions(ids, issueId); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // IssueStats represents issue statistic information. | ||||
| type IssueStats struct { | ||||
| 	OpenCount, ClosedCount int64 | ||||
|  | ||||
							
								
								
									
										155
									
								
								models/user.go
									
									
									
									
									
								
							
							
						
						
									
										155
									
								
								models/user.go
									
									
									
									
									
								
							| @ -56,7 +56,7 @@ type User struct { | ||||
| 	LowerName string `xorm:"UNIQUE NOT NULL"` | ||||
| 	Name      string `xorm:"UNIQUE NOT NULL"` | ||||
| 	FullName  string | ||||
| 	// Email is the primary email address (to be used for communication). | ||||
| 	// Email is the primary email address (to be used for communication) | ||||
| 	Email       string `xorm:"NOT NULL"` | ||||
| 	Passwd      string `xorm:"NOT NULL"` | ||||
| 	LoginType   LoginType | ||||
| @ -78,24 +78,24 @@ type User struct { | ||||
| 	// Maximum repository creation limit, -1 means use gloabl default | ||||
| 	MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"` | ||||
| 
 | ||||
| 	// Permissions. | ||||
| 	// Permissions | ||||
| 	IsActive         bool | ||||
| 	IsAdmin          bool | ||||
| 	AllowGitHook     bool | ||||
| 	AllowImportLocal bool // Allow migrate repository by local path | ||||
| 
 | ||||
| 	// Avatar. | ||||
| 	// Avatar | ||||
| 	Avatar          string `xorm:"VARCHAR(2048) NOT NULL"` | ||||
| 	AvatarEmail     string `xorm:"NOT NULL"` | ||||
| 	UseCustomAvatar bool | ||||
| 
 | ||||
| 	// Counters. | ||||
| 	NumFollowers  int | ||||
| 	NumFollowings int | ||||
| 	NumStars      int | ||||
| 	NumRepos      int | ||||
| 	// Counters | ||||
| 	NumFollowers int | ||||
| 	NumFollowing int `xorm:"NOT NULL"` | ||||
| 	NumStars     int | ||||
| 	NumRepos     int | ||||
| 
 | ||||
| 	// For organization. | ||||
| 	// For organization | ||||
| 	Description string | ||||
| 	NumTeams    int | ||||
| 	NumMembers  int | ||||
| @ -263,6 +263,34 @@ func (u *User) AvatarLink() string { | ||||
| 	return link | ||||
| } | ||||
| 
 | ||||
| // User.GetFollwoers returns range of user's followers. | ||||
| func (u *User) GetFollowers(page int) ([]*User, error) { | ||||
| 	users := make([]*User, 0, ItemsPerPage) | ||||
| 	sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.follow_id=?", u.Id) | ||||
| 	if setting.UsePostgreSQL { | ||||
| 		sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`) | ||||
| 	} else { | ||||
| 		sess = sess.Join("LEFT", "follow", "user.id=follow.user_id") | ||||
| 	} | ||||
| 	return users, sess.Find(&users) | ||||
| } | ||||
| 
 | ||||
| func (u *User) IsFollowing(followID int64) bool { | ||||
| 	return IsFollowing(u.Id, followID) | ||||
| } | ||||
| 
 | ||||
| // GetFollowing returns range of user's following. | ||||
| func (u *User) GetFollowing(page int) ([]*User, error) { | ||||
| 	users := make([]*User, 0, ItemsPerPage) | ||||
| 	sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.user_id=?", u.Id) | ||||
| 	if setting.UsePostgreSQL { | ||||
| 		sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`) | ||||
| 	} else { | ||||
| 		sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id") | ||||
| 	} | ||||
| 	return users, sess.Find(&users) | ||||
| } | ||||
| 
 | ||||
| // NewGitSig generates and returns the signature of given user. | ||||
| func (u *User) NewGitSig() *git.Signature { | ||||
| 	return &git.Signature{ | ||||
| @ -1077,100 +1105,73 @@ func SearchUserByName(opt SearchOption) (us []*User, err error) { | ||||
| 	return us, err | ||||
| } | ||||
| 
 | ||||
| // Follow is connection request for receiving user notification. | ||||
| // ___________    .__  .__ | ||||
| // \_   _____/___ |  | |  |   ______  _  __ | ||||
| //  |    __)/  _ \|  | |  |  /  _ \ \/ \/ / | ||||
| //  |     \(  <_> )  |_|  |_(  <_> )     / | ||||
| //  \___  / \____/|____/____/\____/ \/\_/ | ||||
| //      \/ | ||||
| 
 | ||||
| // Follow represents relations of user and his/her followers. | ||||
| type Follow struct { | ||||
| 	ID       int64 `xorm:"pk autoincr"` | ||||
| 	UserID   int64 `xorm:"UNIQUE(follow)"` | ||||
| 	FollowID int64 `xorm:"UNIQUE(follow)"` | ||||
| } | ||||
| 
 | ||||
| func IsFollowing(userID, followID int64) bool { | ||||
| 	has, _ := x.Get(&Follow{UserID: userID, FollowID: followID}) | ||||
| 	return has | ||||
| } | ||||
| 
 | ||||
| // FollowUser marks someone be another's follower. | ||||
| func FollowUser(userId int64, followId int64) (err error) { | ||||
| func FollowUser(userID, followID int64) (err error) { | ||||
| 	if userID == followID || IsFollowing(userID, followID) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	sess.Begin() | ||||
| 
 | ||||
| 	if _, err = sess.Insert(&Follow{UserID: userId, FollowID: followId}); err != nil { | ||||
| 		sess.Rollback() | ||||
| 	defer sessionRelease(sess) | ||||
| 	if err = sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	rawSql := "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?" | ||||
| 	if _, err = sess.Exec(rawSql, followId); err != nil { | ||||
| 		sess.Rollback() | ||||
| 	if _, err = sess.Insert(&Follow{UserID: userID, FollowID: followID}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	rawSql = "UPDATE `user` SET num_followings = num_followings + 1 WHERE id = ?" | ||||
| 	if _, err = sess.Exec(rawSql, userId); err != nil { | ||||
| 		sess.Rollback() | ||||
| 	if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err = sess.Exec("UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return sess.Commit() | ||||
| } | ||||
| 
 | ||||
| // UnFollowUser unmarks someone be another's follower. | ||||
| func UnFollowUser(userId int64, unFollowId int64) (err error) { | ||||
| 	session := x.NewSession() | ||||
| 	defer session.Close() | ||||
| 	session.Begin() | ||||
| // UnfollowUser unmarks someone be another's follower. | ||||
| func UnfollowUser(userID, followID int64) (err error) { | ||||
| 	if userID == followID || !IsFollowing(userID, followID) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err = session.Delete(&Follow{UserID: userId, FollowID: unFollowId}); err != nil { | ||||
| 		session.Rollback() | ||||
| 	sess := x.NewSession() | ||||
| 	defer sessionRelease(sess) | ||||
| 	if err = sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	rawSql := "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?" | ||||
| 	if _, err = session.Exec(rawSql, unFollowId); err != nil { | ||||
| 		session.Rollback() | ||||
| 	if _, err = sess.Delete(&Follow{UserID: userID, FollowID: followID}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	rawSql = "UPDATE `user` SET num_followings = num_followings - 1 WHERE id = ?" | ||||
| 	if _, err = session.Exec(rawSql, userId); err != nil { | ||||
| 		session.Rollback() | ||||
| 	if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return session.Commit() | ||||
| } | ||||
| 
 | ||||
| func UpdateMentions(userNames []string, issueId int64) error { | ||||
| 	for i := range userNames { | ||||
| 		userNames[i] = strings.ToLower(userNames[i]) | ||||
| 	} | ||||
| 	users := make([]*User, 0, len(userNames)) | ||||
| 
 | ||||
| 	if err := x.Where("lower_name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("lower_name ASC").Find(&users); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	ids := make([]int64, 0, len(userNames)) | ||||
| 	for _, user := range users { | ||||
| 		ids = append(ids, user.Id) | ||||
| 		if !user.IsOrganization() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if user.NumMembers == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		tempIds := make([]int64, 0, user.NumMembers) | ||||
| 		orgUsers, err := GetOrgUsersByOrgId(user.Id) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		for _, orgUser := range orgUsers { | ||||
| 			tempIds = append(tempIds, orgUser.ID) | ||||
| 		} | ||||
| 
 | ||||
| 		ids = append(ids, tempIds...) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := UpdateIssueUsersByMentions(ids, issueId); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| 
 | ||||
| 	if _, err = sess.Exec("UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return sess.Commit() | ||||
| } | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -242,7 +242,8 @@ func Contexter() macaron.Handler { | ||||
| 
 | ||||
| 		ctx.Data["CsrfToken"] = x.GetToken() | ||||
| 		ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + x.GetToken() + `">`) | ||||
| 		log.Debug("CSRF Token: %v | %v", ctx.Data["CsrfToken"], ctx.GetCookie("_csrf")) | ||||
| 		log.Debug("Session ID: %s", sess.ID()) | ||||
| 		log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"]) | ||||
| 
 | ||||
| 		ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton | ||||
| 		ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding | ||||
|  | ||||
| @ -2465,31 +2465,6 @@ footer .container .links > *:first-child { | ||||
| .repository.new.release .prerelease.field { | ||||
|   margin-bottom: 0; | ||||
| } | ||||
| .repository.watchers .list { | ||||
|   padding: 0; | ||||
| } | ||||
| .repository.watchers .list .item { | ||||
|   list-style: none; | ||||
|   width: 32%; | ||||
|   margin: 10px 10px 10px 0; | ||||
|   padding-bottom: 14px; | ||||
|   float: left; | ||||
| } | ||||
| .repository.watchers .list .item .avatar { | ||||
|   width: 48px; | ||||
|   height: 48px; | ||||
|   float: left; | ||||
|   display: block; | ||||
|   margin-right: 10px; | ||||
| } | ||||
| .repository.watchers .list .item .name { | ||||
|   margin-top: 0; | ||||
|   margin-bottom: 0; | ||||
|   font-weight: normal; | ||||
| } | ||||
| .repository.watchers .list .item .meta { | ||||
|   margin-top: 5px; | ||||
| } | ||||
| .repository.forks .list { | ||||
|   margin-top: 0; | ||||
| } | ||||
| @ -2551,6 +2526,31 @@ footer .container .links > *:first-child { | ||||
|   margin-left: 5px; | ||||
|   margin-top: -3px; | ||||
| } | ||||
| .user-cards .list { | ||||
|   padding: 0; | ||||
| } | ||||
| .user-cards .list .item { | ||||
|   list-style: none; | ||||
|   width: 32%; | ||||
|   margin: 10px 10px 10px 0; | ||||
|   padding-bottom: 14px; | ||||
|   float: left; | ||||
| } | ||||
| .user-cards .list .item .avatar { | ||||
|   width: 48px; | ||||
|   height: 48px; | ||||
|   float: left; | ||||
|   display: block; | ||||
|   margin-right: 10px; | ||||
| } | ||||
| .user-cards .list .item .name { | ||||
|   margin-top: 0; | ||||
|   margin-bottom: 0; | ||||
|   font-weight: normal; | ||||
| } | ||||
| .user-cards .list .item .meta { | ||||
|   margin-top: 5px; | ||||
| } | ||||
| #search-repo-box .results, | ||||
| #search-user-box .results { | ||||
|   padding: 0; | ||||
| @ -2862,7 +2862,7 @@ footer .container .links > *:first-child { | ||||
|   margin-left: 5px; | ||||
|   margin-top: -3px; | ||||
| } | ||||
| .user { | ||||
| .user:not(.icon) { | ||||
|   padding-top: 15px; | ||||
|   padding-bottom: 80px; | ||||
| } | ||||
| @ -2893,9 +2893,24 @@ footer .container .links > *:first-child { | ||||
| .user.profile .ui.card .extra.content ul li:not(:last-child) { | ||||
|   border-bottom: 1px solid #eaeaea; | ||||
| } | ||||
| .user.profile .ui.card .extra.content ul li .octicon { | ||||
|   margin-left: 1px; | ||||
|   margin-right: 5px; | ||||
| } | ||||
| .user.profile .ui.card .extra.content ul li.follow .ui.button { | ||||
|   width: 100%; | ||||
| } | ||||
| .user.profile .ui.repository.list { | ||||
|   margin-top: 25px; | ||||
| } | ||||
| .user.followers .header.name { | ||||
|   font-size: 20px; | ||||
|   line-height: 24px; | ||||
|   vertical-align: middle; | ||||
| } | ||||
| .user.followers .follow .ui.button { | ||||
|   padding: 8px 15px; | ||||
| } | ||||
| .dashboard { | ||||
|   padding-top: 15px; | ||||
|   padding-bottom: 80px; | ||||
|  | ||||
| @ -859,35 +859,6 @@ | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	&.watchers { | ||||
| 		.list { | ||||
| 			padding: 0; | ||||
| 
 | ||||
| 			.item { | ||||
| 				list-style: none; | ||||
| 				width: 32%; | ||||
| 				margin: 10px 10px 10px 0; | ||||
| 				padding-bottom: 14px; | ||||
| 				float: left; | ||||
| 
 | ||||
| 				.avatar { | ||||
| 					width: 48px; | ||||
| 					height: 48px; | ||||
| 					float: left; | ||||
| 					display: block; | ||||
| 					margin-right: 10px; | ||||
| 				} | ||||
| 				.name { | ||||
| 					margin-top: 0; | ||||
| 					margin-bottom: 0; | ||||
| 					font-weight: normal; | ||||
| 				} | ||||
| 				.meta { | ||||
| 					margin-top: 5px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	&.forks { | ||||
| 		.list { | ||||
| 			margin-top: 0; | ||||
| @ -982,6 +953,36 @@ | ||||
| } | ||||
| // End of .repository | ||||
| 
 | ||||
| &.user-cards { | ||||
| 	.list { | ||||
| 		padding: 0; | ||||
| 
 | ||||
| 		.item { | ||||
| 			list-style: none; | ||||
| 			width: 32%; | ||||
| 			margin: 10px 10px 10px 0; | ||||
| 			padding-bottom: 14px; | ||||
| 			float: left; | ||||
| 
 | ||||
| 			.avatar { | ||||
| 				width: 48px; | ||||
| 				height: 48px; | ||||
| 				float: left; | ||||
| 				display: block; | ||||
| 				margin-right: 10px; | ||||
| 			} | ||||
| 			.name { | ||||
| 				margin-top: 0; | ||||
| 				margin-bottom: 0; | ||||
| 				font-weight: normal; | ||||
| 			} | ||||
| 			.meta { | ||||
| 				margin-top: 5px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #search-repo-box, | ||||
| #search-user-box { | ||||
| 	.results { | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| .user { | ||||
| 	padding-top: 15px; | ||||
| 	padding-bottom: @footer-margin * 2; | ||||
| 	&:not(.icon) { | ||||
| 		padding-top: 15px; | ||||
| 		padding-bottom: @footer-margin * 2; | ||||
| 	} | ||||
| 
 | ||||
| 	&.settings { | ||||
| 		.list { | ||||
| @ -38,6 +40,17 @@ | ||||
| 						&:not(:last-child) { | ||||
| 							border-bottom: 1px solid #eaeaea; | ||||
| 						} | ||||
| 
 | ||||
| 						.octicon { | ||||
| 							margin-left: 1px; | ||||
| 							margin-right: 5px; | ||||
| 						} | ||||
| 
 | ||||
| 						&.follow { | ||||
| 							.ui.button { | ||||
| 								width: 100%; | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| @ -47,4 +60,18 @@ | ||||
| 			margin-top: 25px; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	&.followers { | ||||
| 		.header.name { | ||||
| 			font-size: 20px; | ||||
| 			line-height: 24px; | ||||
| 			vertical-align: middle; | ||||
| 		} | ||||
| 
 | ||||
| 		.follow { | ||||
| 			.ui.button { | ||||
| 				padding: 8px 15px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -135,19 +135,32 @@ func RegisterRoutes(m *macaron.Macaron) { | ||||
| 		m.Group("/users", func() { | ||||
| 			m.Group("/:username", func() { | ||||
| 				m.Get("/keys", user.ListPublicKeys) | ||||
| 
 | ||||
| 				m.Get("/followers", user.ListFollowers) | ||||
| 				m.Group("/following", func() { | ||||
| 					m.Get("", user.ListFollowing) | ||||
| 					m.Get("/:target", user.CheckFollowing) | ||||
| 				}) | ||||
| 			}) | ||||
| 		}, ReqToken()) | ||||
| 
 | ||||
| 		m.Group("/user", func() { | ||||
| 			m.Combo("/emails").Get(user.ListEmails). | ||||
| 				Post(bind(api.CreateEmailOption{}), user.AddEmail). | ||||
| 				Delete(bind(api.CreateEmailOption{}), user.DeleteEmail) | ||||
| 
 | ||||
| 			m.Get("/followers", user.ListMyFollowers) | ||||
| 			m.Group("/following", func() { | ||||
| 				m.Get("", user.ListMyFollowing) | ||||
| 				m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow) | ||||
| 			}) | ||||
| 
 | ||||
| 			m.Group("/keys", func() { | ||||
| 				m.Combo("").Get(user.ListMyPublicKeys). | ||||
| 					Post(bind(api.CreateKeyOption{}), user.CreatePublicKey) | ||||
| 				m.Combo("/:id").Get(user.GetPublicKey). | ||||
| 					Delete(user.DeletePublicKey) | ||||
| 			}) | ||||
| 			m.Combo("/emails").Get(user.ListEmails). | ||||
| 				Post(bind(api.CreateEmailOption{}), user.AddEmail). | ||||
| 				Delete(bind(api.CreateEmailOption{}), user.DeleteEmail) | ||||
| 		}, ReqToken()) | ||||
| 
 | ||||
| 		// Repositories | ||||
|  | ||||
							
								
								
									
										121
									
								
								routers/api/v1/user/followers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								routers/api/v1/user/followers.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| // Copyright 2015 The Gogs Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package user | ||||
| 
 | ||||
| import ( | ||||
| 	api "github.com/gogits/go-gogs-client" | ||||
| 
 | ||||
| 	"github.com/gogits/gogs/models" | ||||
| 	"github.com/gogits/gogs/modules/middleware" | ||||
| 	"github.com/gogits/gogs/routers/api/v1/convert" | ||||
| ) | ||||
| 
 | ||||
| func responseApiUsers(ctx *middleware.Context, users []*models.User) { | ||||
| 	apiUsers := make([]*api.User, len(users)) | ||||
| 	for i := range users { | ||||
| 		apiUsers[i] = convert.ToApiUser(users[i]) | ||||
| 	} | ||||
| 	ctx.JSON(200, &apiUsers) | ||||
| } | ||||
| 
 | ||||
| func listUserFollowers(ctx *middleware.Context, u *models.User) { | ||||
| 	users, err := u.GetFollowers(ctx.QueryInt("page")) | ||||
| 	if err != nil { | ||||
| 		ctx.APIError(500, "GetUserFollowers", err) | ||||
| 		return | ||||
| 	} | ||||
| 	responseApiUsers(ctx, users) | ||||
| } | ||||
| 
 | ||||
| func ListMyFollowers(ctx *middleware.Context) { | ||||
| 	listUserFollowers(ctx, ctx.User) | ||||
| } | ||||
| 
 | ||||
| // https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-followers-of-a-user | ||||
| func ListFollowers(ctx *middleware.Context) { | ||||
| 	u := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	listUserFollowers(ctx, u) | ||||
| } | ||||
| 
 | ||||
| func listUserFollowing(ctx *middleware.Context, u *models.User) { | ||||
| 	users, err := u.GetFollowing(ctx.QueryInt("page")) | ||||
| 	if err != nil { | ||||
| 		ctx.APIError(500, "GetFollowing", err) | ||||
| 		return | ||||
| 	} | ||||
| 	responseApiUsers(ctx, users) | ||||
| } | ||||
| 
 | ||||
| func ListMyFollowing(ctx *middleware.Context) { | ||||
| 	listUserFollowing(ctx, ctx.User) | ||||
| } | ||||
| 
 | ||||
| // https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-users-followed-by-another-user | ||||
| func ListFollowing(ctx *middleware.Context) { | ||||
| 	u := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	listUserFollowing(ctx, u) | ||||
| } | ||||
| 
 | ||||
| func checkUserFollowing(ctx *middleware.Context, u *models.User, followID int64) { | ||||
| 	if u.IsFollowing(followID) { | ||||
| 		ctx.Status(204) | ||||
| 	} else { | ||||
| 		ctx.Error(404) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-you-are-following-a-user | ||||
| func CheckMyFollowing(ctx *middleware.Context) { | ||||
| 	target := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	checkUserFollowing(ctx, ctx.User, target.Id) | ||||
| } | ||||
| 
 | ||||
| // https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-one-user-follows-another | ||||
| func CheckFollowing(ctx *middleware.Context) { | ||||
| 	u := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	target := GetUserByParamsName(ctx, ":target") | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	checkUserFollowing(ctx, u, target.Id) | ||||
| } | ||||
| 
 | ||||
| // https://github.com/gogits/go-gogs-client/wiki/Users-Followers#follow-a-user | ||||
| func Follow(ctx *middleware.Context) { | ||||
| 	target := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	if err := models.FollowUser(ctx.User.Id, target.Id); err != nil { | ||||
| 		ctx.APIError(500, "FollowUser", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Status(204) | ||||
| } | ||||
| 
 | ||||
| // https://github.com/gogits/go-gogs-client/wiki/Users-Followers#unfollow-a-user | ||||
| func Unfollow(ctx *middleware.Context) { | ||||
| 	target := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	if err := models.UnfollowUser(ctx.User.Id, target.Id); err != nil { | ||||
| 		ctx.APIError(500, "UnfollowUser", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Status(204) | ||||
| } | ||||
| @ -244,11 +244,7 @@ func Action(ctx *middleware.Context) { | ||||
| 	} | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		log.Error(4, "Action(%s): %v", ctx.Params(":action"), err) | ||||
| 		ctx.JSON(200, map[string]interface{}{ | ||||
| 			"ok":  false, | ||||
| 			"err": err.Error(), | ||||
| 		}) | ||||
| 		ctx.Handle(500, fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -217,7 +217,7 @@ func Home(ctx *middleware.Context) { | ||||
| 	ctx.HTML(200, HOME) | ||||
| } | ||||
| 
 | ||||
| func renderItems(ctx *middleware.Context, total int, getter func(page int) ([]*models.User, error)) { | ||||
| func RenderUserCards(ctx *middleware.Context, total int, getter func(page int) ([]*models.User, error), tpl base.TplName) { | ||||
| 	page := ctx.QueryInt("page") | ||||
| 	if page <= 0 { | ||||
| 		page = 1 | ||||
| @ -230,21 +230,23 @@ func renderItems(ctx *middleware.Context, total int, getter func(page int) ([]*m | ||||
| 		ctx.Handle(500, "getter", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["Watchers"] = items | ||||
| 	ctx.Data["Cards"] = items | ||||
| 
 | ||||
| 	ctx.HTML(200, WATCHERS) | ||||
| 	ctx.HTML(200, tpl) | ||||
| } | ||||
| 
 | ||||
| func Watchers(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("repo.watchers") | ||||
| 	ctx.Data["CardsTitle"] = ctx.Tr("repo.watchers") | ||||
| 	ctx.Data["PageIsWatchers"] = true | ||||
| 	renderItems(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers) | ||||
| 	RenderUserCards(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers, WATCHERS) | ||||
| } | ||||
| 
 | ||||
| func Stars(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("repo.stargazers") | ||||
| 	ctx.Data["CardsTitle"] = ctx.Tr("repo.stargazers") | ||||
| 	ctx.Data["PageIsStargazers"] = true | ||||
| 	renderItems(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers) | ||||
| 	RenderUserCards(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers, WATCHERS) | ||||
| } | ||||
| 
 | ||||
| func Forks(ctx *middleware.Context) { | ||||
|  | ||||
| @ -7,7 +7,6 @@ package user | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/Unknwon/com" | ||||
| 	"github.com/Unknwon/paginater" | ||||
| @ -21,7 +20,6 @@ import ( | ||||
| const ( | ||||
| 	DASHBOARD base.TplName = "user/dashboard/dashboard" | ||||
| 	ISSUES    base.TplName = "user/dashboard/issues" | ||||
| 	STARS     base.TplName = "user/stars" | ||||
| 	PROFILE   base.TplName = "user/profile" | ||||
| 	ORG_HOME  base.TplName = "org/home" | ||||
| ) | ||||
| @ -338,67 +336,6 @@ func showOrgProfile(ctx *middleware.Context) { | ||||
| 	ctx.HTML(200, ORG_HOME) | ||||
| } | ||||
| 
 | ||||
| func Profile(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = "Profile" | ||||
| 	ctx.Data["PageIsUserProfile"] = true | ||||
| 
 | ||||
| 	uname := ctx.Params(":username") | ||||
| 	// Special handle for FireFox requests favicon.ico. | ||||
| 	if uname == "favicon.ico" { | ||||
| 		ctx.Redirect(setting.AppSubUrl + "/img/favicon.png") | ||||
| 		return | ||||
| 	} else if strings.HasSuffix(uname, ".png") { | ||||
| 		ctx.Error(404) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	isShowKeys := false | ||||
| 	if strings.HasSuffix(uname, ".keys") { | ||||
| 		isShowKeys = true | ||||
| 		uname = strings.TrimSuffix(uname, ".keys") | ||||
| 	} | ||||
| 
 | ||||
| 	u, err := models.GetUserByName(uname) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrUserNotExist(err) { | ||||
| 			ctx.Handle(404, "GetUserByName", err) | ||||
| 		} else { | ||||
| 			ctx.Handle(500, "GetUserByName", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Show SSH keys. | ||||
| 	if isShowKeys { | ||||
| 		ShowSSHKeys(ctx, u.Id) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if u.IsOrganization() { | ||||
| 		showOrgProfile(ctx) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["Owner"] = u | ||||
| 
 | ||||
| 	tab := ctx.Query("tab") | ||||
| 	ctx.Data["TabName"] = tab | ||||
| 	switch tab { | ||||
| 	case "activity": | ||||
| 		retrieveFeeds(ctx, u.Id, 0, true) | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
| 		} | ||||
| 	default: | ||||
| 		ctx.Data["Repos"], err = models.GetRepositories(u.Id, ctx.IsSigned && ctx.User.Id == u.Id) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "GetRepositories", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.HTML(200, PROFILE) | ||||
| } | ||||
| 
 | ||||
| func Email2User(ctx *middleware.Context) { | ||||
| 	u, err := models.GetUserByEmail(ctx.Query("email")) | ||||
| 	if err != nil { | ||||
|  | ||||
							
								
								
									
										145
									
								
								routers/user/profile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								routers/user/profile.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,145 @@ | ||||
| // Copyright 2015 The Gogs Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package user | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/gogits/gogs/models" | ||||
| 	"github.com/gogits/gogs/modules/base" | ||||
| 	"github.com/gogits/gogs/modules/middleware" | ||||
| 	"github.com/gogits/gogs/modules/setting" | ||||
| 	"github.com/gogits/gogs/routers/repo" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	FOLLOWERS base.TplName = "user/meta/followers" | ||||
| 	STARS     base.TplName = "user/meta/stars" | ||||
| ) | ||||
| 
 | ||||
| // GetUserByParams returns user whose name is presented in URL paramenter. | ||||
| func GetUserByParams(ctx *middleware.Context) *models.User { | ||||
| 	user, err := models.GetUserByName(ctx.Params(":username")) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrUserNotExist(err) { | ||||
| 			ctx.Error(404) | ||||
| 		} else { | ||||
| 			ctx.Handle(500, "GetUserByName", err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return user | ||||
| } | ||||
| 
 | ||||
| func Profile(ctx *middleware.Context) { | ||||
| 	uname := ctx.Params(":username") | ||||
| 	// Special handle for FireFox requests favicon.ico. | ||||
| 	if uname == "favicon.ico" { | ||||
| 		ctx.Redirect(setting.AppSubUrl + "/img/favicon.png") | ||||
| 		return | ||||
| 	} else if strings.HasSuffix(uname, ".png") { | ||||
| 		ctx.Error(404) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	isShowKeys := false | ||||
| 	if strings.HasSuffix(uname, ".keys") { | ||||
| 		isShowKeys = true | ||||
| 	} | ||||
| 
 | ||||
| 	u := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Show SSH keys. | ||||
| 	if isShowKeys { | ||||
| 		ShowSSHKeys(ctx, u.Id) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if u.IsOrganization() { | ||||
| 		showOrgProfile(ctx) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Data["Title"] = u.DisplayName() | ||||
| 	ctx.Data["PageIsUserProfile"] = true | ||||
| 	ctx.Data["Owner"] = u | ||||
| 
 | ||||
| 	tab := ctx.Query("tab") | ||||
| 	ctx.Data["TabName"] = tab | ||||
| 	switch tab { | ||||
| 	case "activity": | ||||
| 		retrieveFeeds(ctx, u.Id, 0, true) | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
| 		} | ||||
| 	default: | ||||
| 		var err error | ||||
| 		ctx.Data["Repos"], err = models.GetRepositories(u.Id, ctx.IsSigned && ctx.User.Id == u.Id) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "GetRepositories", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.HTML(200, PROFILE) | ||||
| } | ||||
| 
 | ||||
| func Followers(ctx *middleware.Context) { | ||||
| 	u := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["Title"] = u.DisplayName() | ||||
| 	ctx.Data["CardsTitle"] = ctx.Tr("user.followers") | ||||
| 	ctx.Data["PageIsFollowers"] = true | ||||
| 	ctx.Data["Owner"] = u | ||||
| 	repo.RenderUserCards(ctx, u.NumFollowers, u.GetFollowers, FOLLOWERS) | ||||
| } | ||||
| 
 | ||||
| func Following(ctx *middleware.Context) { | ||||
| 	u := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["Title"] = u.DisplayName() | ||||
| 	ctx.Data["CardsTitle"] = ctx.Tr("user.following") | ||||
| 	ctx.Data["PageIsFollowing"] = true | ||||
| 	ctx.Data["Owner"] = u | ||||
| 	repo.RenderUserCards(ctx, u.NumFollowing, u.GetFollowing, FOLLOWERS) | ||||
| } | ||||
| 
 | ||||
| func Stars(ctx *middleware.Context) { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func Action(ctx *middleware.Context) { | ||||
| 	u := GetUserByParams(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	var err error | ||||
| 	switch ctx.Params(":action") { | ||||
| 	case "follow": | ||||
| 		err = models.FollowUser(ctx.User.Id, u.Id) | ||||
| 	case "unfollow": | ||||
| 		err = models.UnfollowUser(ctx.User.Id, u.Id) | ||||
| 	} | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	redirectTo := ctx.Query("redirect_to") | ||||
| 	if len(redirectTo) == 0 { | ||||
| 		redirectTo = u.HomeLink() | ||||
| 	} | ||||
| 	ctx.Redirect(redirectTo) | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| 0.8.12.1219 | ||||
| 0.8.13.1221 | ||||
							
								
								
									
										47
									
								
								templates/repo/user_cards.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								templates/repo/user_cards.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| <div class="ui container user-cards"> | ||||
| 	<h2 class="ui dividing header"> | ||||
| 		{{.CardsTitle}} | ||||
| 	</h2> | ||||
| 	<ul class="list"> | ||||
| 		{{range .Cards}} | ||||
| 			<li class="item ui segment"> | ||||
| 				<a href="{{.HomeLink}}"> | ||||
| 					<img class="avatar" src="{{.AvatarLink}}"/> | ||||
| 				</a> | ||||
| 				<h3 class="name"><a href="{{.HomeLink}}">{{.DisplayName}}</a></h3> | ||||
| 
 | ||||
| 				<div class="meta"> | ||||
| 					{{if .Website}} | ||||
| 						<span class="icon octicon octicon-link"></span> <a href="{{.Website}}" target="_blank">{{.Website}}</a> | ||||
| 					{{else if .Location}} | ||||
| 						<span class="icon octicon octicon-location"></span> {{.Location}} | ||||
| 					{{else}} | ||||
| 						<span class="icon octicon octicon-clock"></span> {{$.i18n.Tr "user.join_on"}} {{DateFmtShort .Created}} | ||||
| 					{{end}} | ||||
| 				</div> | ||||
| 			</li> | ||||
| 		{{end}} | ||||
| 	</ul> | ||||
| 
 | ||||
| 	{{with .Page}} | ||||
| 		{{if gt .TotalPages 1}} | ||||
| 			<div class="center page buttons"> | ||||
| 				<div class="ui borderless pagination menu"> | ||||
| 					<a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}"{{end}}> | ||||
| 						<i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}} | ||||
| 					</a> | ||||
| 					{{range .Pages}} | ||||
| 						{{if eq .Num -1}} | ||||
| 							<a class="disabled item">...</a> | ||||
| 						{{else}} | ||||
| 							<a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}"{{end}}>{{.Num}}</a> | ||||
| 						{{end}} | ||||
| 					{{end}} | ||||
| 					<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}"{{end}}> | ||||
| 						{{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i> | ||||
| 					</a> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		{{end}} | ||||
| 	{{end}} | ||||
| </div> | ||||
| @ -1,56 +1,6 @@ | ||||
| {{template "base/head" .}} | ||||
| <div class="repository watchers"> | ||||
| 	{{template "repo/header" .}} | ||||
| 	<div class="ui container"> | ||||
| 		<h2 class="ui dividing header"> | ||||
| 			{{if .PageIsWatchers}} | ||||
| 				{{.i18n.Tr "repo.watchers"}} | ||||
| 			{{else}} | ||||
| 				{{.i18n.Tr "repo.stargazers"}} | ||||
| 			{{end}} | ||||
| 		</h2> | ||||
| 		<ul class="list"> | ||||
| 			{{range .Watchers}} | ||||
| 				<li class="item ui segment"> | ||||
| 					<a href="{{.HomeLink}}"> | ||||
| 						<img class="avatar" src="{{.AvatarLink}}"/> | ||||
| 					</a> | ||||
| 					<h3 class="name"><a href="{{.HomeLink}}">{{.DisplayName}}</a></h3> | ||||
| 
 | ||||
| 					<div class="meta"> | ||||
| 						{{if .Website}} | ||||
| 							<span class="icon octicon octicon-link"></span> <a href="{{.Website}}" target="_blank">{{.Website}}</a> | ||||
| 						{{else if .Location}} | ||||
| 							<span class="icon octicon octicon-location"></span> {{.Location}} | ||||
| 						{{else}} | ||||
| 							<span class="icon octicon octicon-clock"></span> {{$.i18n.Tr "user.join_on"}} {{DateFmtShort .Created}} | ||||
| 						{{end}} | ||||
| 					</div> | ||||
| 				</li> | ||||
| 			{{end}} | ||||
| 		</ul> | ||||
| 
 | ||||
| 		{{with .Page}} | ||||
| 			{{if gt .TotalPages 1}} | ||||
| 				<div class="center page buttons"> | ||||
| 					<div class="ui borderless pagination menu"> | ||||
| 						<a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}"{{end}}> | ||||
| 							<i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}} | ||||
| 						</a> | ||||
| 						{{range .Pages}} | ||||
| 							{{if eq .Num -1}} | ||||
| 								<a class="disabled item">...</a> | ||||
| 							{{else}} | ||||
| 								<a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}"{{end}}>{{.Num}}</a> | ||||
| 							{{end}} | ||||
| 						{{end}} | ||||
| 						<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}"{{end}}> | ||||
| 							{{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i> | ||||
| 						</a> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			{{end}} | ||||
| 		{{end}} | ||||
| 	</div> | ||||
| 	{{template "repo/user_cards" .}} | ||||
| </div> | ||||
| {{template "base/footer" .}} | ||||
|  | ||||
							
								
								
									
										6
									
								
								templates/user/meta/followers.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								templates/user/meta/followers.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| {{template "base/head" .}} | ||||
| <div class="user followers"> | ||||
| 	{{template "user/meta/header" .}} | ||||
| 	{{template "repo/user_cards" .}} | ||||
| </div> | ||||
| {{template "base/footer" .}} | ||||
							
								
								
									
										25
									
								
								templates/user/meta/header.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								templates/user/meta/header.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| {{with .Owner}} | ||||
| <div class="ui container"> | ||||
| 	<img class="ui avatar image" src="{{.AvatarLink}}"> | ||||
| 	<span class="header name"> | ||||
| 		<a href="{{.HomeLink}}">{{.Name}}</a> | ||||
| 		{{with .FullName}}({{.}}){{end}} | ||||
| 	</span> | ||||
| 
 | ||||
| 	<div class="ui right"> | ||||
| 		 | ||||
| 		{{if or $.PageIsFollowers $.PageIsFollowing}} | ||||
| 			{{if and $.IsSigned (ne $.SignedUserName .Name)}} | ||||
| 				<div class="follow"> | ||||
| 					{{if $.SignedUser.IsFollowing .Id}} | ||||
| 					<a class="ui small basic red button" href="{{.HomeLink}}/action/unfollow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{$.i18n.Tr "user.unfollow"}}</a> | ||||
| 					{{else}} | ||||
| 					<a class="ui small basic green button" href="{{.HomeLink}}/action/follow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{$.i18n.Tr "user.follow"}}</a> | ||||
| 					{{end}} | ||||
| 				</div> | ||||
| 			{{end}} | ||||
| 		{{end}} | ||||
| 	</div> | ||||
| </div> | ||||
| {{end}} | ||||
| <div class="ui divider"></div> | ||||
							
								
								
									
										0
									
								
								templates/user/meta/stars.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								templates/user/meta/stars.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -39,6 +39,33 @@ | ||||
| 								</li> | ||||
| 							{{end}} | ||||
| 							<li><i class="icon octicon octicon-clock"></i> {{.i18n.Tr "user.join_on"}} {{DateFmtShort .Owner.Created}}</li> | ||||
| 							<li> | ||||
| 								<i class="user icon"></i> | ||||
| 								<a href="{{.Owner.HomeLink}}/followers"> | ||||
| 									{{.Owner.NumFollowers}} {{.i18n.Tr "user.followers"}} | ||||
| 								</a> | ||||
| 								- | ||||
| 								<a href="{{.Owner.HomeLink}}/following"> | ||||
| 									{{.Owner.NumFollowing}} {{.i18n.Tr "user.following"}} | ||||
| 								</a> | ||||
| 							</li> | ||||
| 							{{/* | ||||
| 							<li> | ||||
| 								<i class="octicon octicon-star"></i> | ||||
| 								<a href="{{.Owner.HomeLink}}/stars"> | ||||
| 									{{.Owner.NumStars}} {{.i18n.Tr "user.starred"}} | ||||
| 								</a> | ||||
| 							</li> | ||||
| 							*/}} | ||||
| 							{{if and .IsSigned (ne .SignedUserName .Owner.Name)}} | ||||
| 							<li class="follow"> | ||||
| 								{{if .SignedUser.IsFollowing .Owner.Id}} | ||||
| 								<a class="ui basic red button" href="{{.Link}}/action/unfollow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{.i18n.Tr "user.unfollow"}}</a> | ||||
| 								{{else}} | ||||
| 								<a class="ui basic green button" href="{{.Link}}/action/follow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{.i18n.Tr "user.follow"}}</a> | ||||
| 								{{end}} | ||||
| 							</li> | ||||
| 							{{end}} | ||||
| 						</ul> | ||||
| 					</div> | ||||
| 				</div> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user