Browse Source

#13 finish user and repository search

Both are possible on explore and admin panel
Unknwon 8 năm trước cách đây
mục cha
commit
2bf8494332

+ 1 - 1
README.md

@@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
 
 ![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
 
-##### Current version: 0.9.5
+##### Current version: 0.9.6
 
 | Web | UI  | Preview  |
 |:-------------:|:-------:|:-------:|

+ 4 - 1
cmd/web.go

@@ -193,7 +193,10 @@ func runWeb(ctx *cli.Context) {
 	// Especially some AJAX requests, we can reduce middleware number to improve performance.
 	// Routers.
 	m.Get("/", ignSignIn, routers.Home)
-	m.Get("/explore", ignSignIn, routers.Explore)
+	m.Group("/explore", func() {
+		m.Get("/repos", routers.ExploreRepos)
+		m.Get("/users", routers.ExploreUsers)
+	}, ignSignIn)
 	m.Combo("/install", routers.InstallInit).Get(routers.Install).
 		Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
 	m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)

+ 2 - 7
conf/locale/locale_en-US.ini

@@ -44,13 +44,6 @@ issues = Issues
 
 cancel = Cancel
 
-[search]
-search = Search...
-repository = Repository
-user = User
-issue = Issue
-code = Code
-
 [install]
 install = Installation
 title = Install Steps For First-time Run
@@ -140,6 +133,8 @@ issues.in_your_repos = In your repositories
 
 [explore]
 repos = Repositories
+users = Users
+search = Search
 
 [auth]
 create_new_account = Create New Account

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.9.5.0311"
+const APP_VER = "0.9.6.0311"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 1 - 1
models/org.go

@@ -169,7 +169,7 @@ func GetOrgByName(name string) (*User, error) {
 	}
 	u := &User{
 		LowerName: strings.ToLower(name),
-		Type:      ORGANIZATION,
+		Type:      USER_TYPE_ORGANIZATION,
 	}
 	has, err := x.Get(u)
 	if err != nil {

+ 49 - 24
models/repo.go

@@ -1049,11 +1049,16 @@ func CountPublicRepositories() int64 {
 	return countRepositories(false)
 }
 
+func Repositories(page, pageSize int) (_ []*Repository, err error) {
+	repos := make([]*Repository, 0, pageSize)
+	return repos, x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos)
+}
+
 // RepositoriesWithUsers returns number of repos in given page.
 func RepositoriesWithUsers(page, pageSize int) (_ []*Repository, err error) {
-	repos := make([]*Repository, 0, pageSize)
-	if err = x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos); err != nil {
-		return nil, err
+	repos, err := Repositories(page, pageSize)
+	if err != nil {
+		return nil, fmt.Errorf("Repositories: %v", err)
 	}
 
 	for i := range repos {
@@ -1474,9 +1479,9 @@ func GetRepositories(uid int64, private bool) ([]*Repository, error) {
 }
 
 // GetRecentUpdatedRepositories returns the list of repositories that are recently updated.
-func GetRecentUpdatedRepositories(page int) (repos []*Repository, err error) {
-	return repos, x.Limit(setting.ExplorePagingNum, (page-1)*setting.ExplorePagingNum).
-		Where("is_private=?", false).Limit(setting.ExplorePagingNum).Desc("updated_unix").Find(&repos)
+func GetRecentUpdatedRepositories(page, pageSize int) (repos []*Repository, err error) {
+	return repos, x.Limit(pageSize, (page-1)*pageSize).
+		Where("is_private=?", false).Limit(pageSize).Desc("updated_unix").Find(&repos)
 }
 
 func getRepositoryCount(e Engine, u *User) (int64, error) {
@@ -1488,32 +1493,52 @@ func GetRepositoryCount(u *User) (int64, error) {
 	return getRepositoryCount(x, u)
 }
 
-type SearchOption struct {
-	Keyword string
-	Uid     int64
-	Limit   int
-	Private bool
+type SearchRepoOptions struct {
+	Keyword  string
+	OwnerID  int64
+	OrderBy  string
+	Private  bool // Include private repositories in results
+	Page     int
+	PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
 }
 
-// SearchRepositoryByName returns given number of repositories whose name contains keyword.
-func SearchRepositoryByName(opt SearchOption) (repos []*Repository, err error) {
-	if len(opt.Keyword) == 0 {
-		return repos, nil
+// SearchRepositoryByName takes keyword and part of repository name to search,
+// it returns results in given range and number of total results.
+func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, _ int64, _ error) {
+	if len(opts.Keyword) == 0 {
+		return repos, 0, nil
+	}
+	opts.Keyword = strings.ToLower(opts.Keyword)
+
+	if opts.PageSize <= 0 || opts.PageSize > setting.ExplorePagingNum {
+		opts.PageSize = setting.ExplorePagingNum
+	}
+	if opts.Page <= 0 {
+		opts.Page = 1
 	}
-	opt.Keyword = strings.ToLower(opt.Keyword)
 
-	repos = make([]*Repository, 0, opt.Limit)
+	repos = make([]*Repository, 0, opts.PageSize)
 
-	// Append conditions.
-	sess := x.Limit(opt.Limit)
-	if opt.Uid > 0 {
-		sess.Where("owner_id=?", opt.Uid)
+	// Append conditions
+	sess := x.Where("lower_name like ?", "%"+opts.Keyword+"%")
+	if opts.OwnerID > 0 {
+		sess.And("owner_id = ?", opts.OwnerID)
 	}
-	if !opt.Private {
+	if !opts.Private {
 		sess.And("is_private=?", false)
 	}
-	sess.And("lower_name like ?", "%"+opt.Keyword+"%").Find(&repos)
-	return repos, err
+	if len(opts.OrderBy) > 0 {
+		sess.OrderBy(opts.OrderBy)
+	}
+
+	var countSess xorm.Session
+	countSess = *sess
+	count, err := countSess.Count(new(Repository))
+	if err != nil {
+		return nil, 0, fmt.Errorf("Count: %v", err)
+	}
+
+	return repos, count, sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&repos)
 }
 
 // DeleteRepositoryArchives deletes all repositories' archives.

+ 40 - 11
models/user.go

@@ -36,8 +36,8 @@ import (
 type UserType int
 
 const (
-	INDIVIDUAL UserType = iota // Historic reason to make it starts at 0.
-	ORGANIZATION
+	USER_TYPE_INDIVIDUAL UserType = iota // Historic reason to make it starts at 0.
+	USER_TYPE_ORGANIZATION
 )
 
 var (
@@ -389,7 +389,7 @@ func (u *User) IsWriterOfRepo(repo *Repository) bool {
 
 // IsOrganization returns true if user is actually a organization.
 func (u *User) IsOrganization() bool {
-	return u.Type == ORGANIZATION
+	return u.Type == USER_TYPE_ORGANIZATION
 }
 
 // IsUserOrgOwner returns true if user is in the owner team of given organization.
@@ -1114,16 +1114,45 @@ func GetUserByEmail(email string) (*User, error) {
 	return nil, ErrUserNotExist{0, email}
 }
 
-// SearchUserByName returns given number of users whose name contains keyword.
-func SearchUserByName(opt SearchOption) (us []*User, err error) {
-	if len(opt.Keyword) == 0 {
-		return us, nil
+type SearchUserOptions struct {
+	Keyword  string
+	Type     UserType
+	OrderBy  string
+	Page     int
+	PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
+}
+
+// SearchUserByName takes keyword and part of user name to search,
+// it returns results in given range and number of total results.
+func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
+	if len(opts.Keyword) == 0 {
+		return users, 0, nil
+	}
+	opts.Keyword = strings.ToLower(opts.Keyword)
+
+	if opts.PageSize <= 0 || opts.PageSize > setting.ExplorePagingNum {
+		opts.PageSize = setting.ExplorePagingNum
+	}
+	if opts.Page <= 0 {
+		opts.Page = 1
+	}
+
+	users = make([]*User, 0, opts.PageSize)
+	// Append conditions
+	fmt.Println(opts.Type)
+	sess := x.Where("lower_name like ?", "%"+opts.Keyword+"%").And("type = ?", opts.Type)
+	if len(opts.OrderBy) > 0 {
+		sess.OrderBy(opts.OrderBy)
+	}
+
+	var countSess xorm.Session
+	countSess = *sess
+	count, err := countSess.Count(new(User))
+	if err != nil {
+		return nil, 0, fmt.Errorf("Count: %v", err)
 	}
-	opt.Keyword = strings.ToLower(opt.Keyword)
 
-	us = make([]*User, 0, opt.Limit)
-	err = x.Limit(opt.Limit).Where("type=0").And("lower_name like ?", "%"+opt.Keyword+"%").Find(&us)
-	return us, err
+	return users, count, sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&users)
 }
 
 // ___________    .__  .__

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 112 - 112
modules/bindata/bindata.go


+ 7 - 7
public/config.codekit

@@ -441,30 +441,30 @@
 		"outputStyle": 1,
 		"syntaxCheckerStyle": 1
 		},
-	"\/plugins\/highlight-9.1.0\/default.css": {
+	"\/plugins\/highlight-9.2.0\/default.css": {
 		"fileType": 16,
 		"ignore": 0,
 		"ignoreWasSetByUser": 0,
-		"inputAbbreviatedPath": "\/plugins\/highlight-9.1.0\/default.css",
+		"inputAbbreviatedPath": "\/plugins\/highlight-9.2.0\/default.css",
 		"outputAbbreviatedPath": "No Output Path",
 		"outputPathIsOutsideProject": 0,
 		"outputPathIsSetByUser": 0
 		},
-	"\/plugins\/highlight-9.1.0\/github.css": {
+	"\/plugins\/highlight-9.2.0\/github.css": {
 		"fileType": 16,
 		"ignore": 0,
 		"ignoreWasSetByUser": 0,
-		"inputAbbreviatedPath": "\/plugins\/highlight-9.1.0\/github.css",
+		"inputAbbreviatedPath": "\/plugins\/highlight-9.2.0\/github.css",
 		"outputAbbreviatedPath": "No Output Path",
 		"outputPathIsOutsideProject": 0,
 		"outputPathIsSetByUser": 0
 		},
-	"\/plugins\/highlight-9.1.0\/highlight.pack.js": {
+	"\/plugins\/highlight-9.2.0\/highlight.pack.js": {
 		"fileType": 64,
 		"ignore": 0,
 		"ignoreWasSetByUser": 0,
-		"inputAbbreviatedPath": "\/plugins\/highlight-9.1.0\/highlight.pack.js",
-		"outputAbbreviatedPath": "\/plugins\/highlight-9.1.0\/min\/highlight.pack-min.js",
+		"inputAbbreviatedPath": "\/plugins\/highlight-9.2.0\/highlight.pack.js",
+		"outputAbbreviatedPath": "\/plugins\/highlight-9.2.0\/min\/highlight.pack-min.js",
 		"outputPathIsOutsideProject": 0,
 		"outputPathIsSetByUser": 0,
 		"outputStyle": 1,

+ 27 - 0
public/css/gogs.css

@@ -2594,6 +2594,10 @@ footer .container .links > *:first-child {
   padding-top: 15px;
   padding-bottom: 80px;
 }
+.explore .navbar .octicon {
+  width: 16px;
+  text-align: center;
+}
 .ui.repository.list .item {
   padding-bottom: 25px;
 }
@@ -2620,3 +2624,26 @@ footer .container .links > *:first-child {
   font-size: 12px;
   color: #808080;
 }
+.ui.user.list .item {
+  padding-bottom: 25px;
+}
+.ui.user.list .item:not(:first-child) {
+  border-top: 1px solid #eee;
+  padding-top: 25px;
+}
+.ui.user.list .item .ui.avatar.image {
+  width: 40px;
+  height: 40px;
+}
+.ui.user.list .item .description {
+  margin-top: 5px;
+}
+.ui.user.list .item .description .octicon:not(:first-child) {
+  margin-left: 5px;
+}
+.ui.user.list .item .description a {
+  color: #333;
+}
+.ui.user.list .item .description a:hover {
+  text-decoration: underline;
+}

+ 38 - 0
public/less/_explore.less

@@ -1,6 +1,13 @@
 .explore {
 	padding-top: 15px;
 	padding-bottom: @footer-margin * 2;
+
+	.navbar {
+		.octicon {
+			width: 16px;
+			text-align: center;
+		}
+	}
 }
 
 .ui.repository.list {
@@ -35,3 +42,34 @@
 		}
 	}
 }
+
+.ui.user.list {
+	.item {
+		padding-bottom: 25px;
+
+		&:not(:first-child) {
+			border-top: 1px solid #eee;
+			padding-top: 25px;
+		}
+
+		.ui.avatar.image {
+			width: 40px;
+			height: 40px;
+		}
+
+		.description {
+			margin-top: 5px;
+
+			.octicon:not(:first-child) {
+				margin-left: 5px;
+			}
+
+			a {
+				color: #333;
+				&:hover {
+					text-decoration: underline;
+				}
+			}
+		}
+	}
+}

+ 3 - 20
routers/admin/orgs.go

@@ -5,12 +5,11 @@
 package admin
 
 import (
-	"github.com/Unknwon/paginater"
-
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/context"
 	"github.com/gogits/gogs/modules/setting"
+	"github.com/gogits/gogs/routers"
 )
 
 const (
@@ -22,22 +21,6 @@ func Organizations(ctx *context.Context) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminOrganizations"] = true
 
-	total := models.CountOrganizations()
-	page := ctx.QueryInt("page")
-	if page <= 1 {
-		page = 1
-	}
-	ctx.Data["Page"] = paginater.New(int(total), setting.AdminOrgPagingNum, page, 5)
-
-	orgs, err := models.Organizations(page, setting.AdminOrgPagingNum)
-
-	if err != nil {
-		ctx.Handle(500, "Organizations", err)
-		return
-	}
-
-	ctx.Data["Orgs"] = orgs
-	ctx.Data["Total"] = total
-
-	ctx.HTML(200, ORGS)
+	routers.RenderUserSearch(ctx, models.USER_TYPE_ORGANIZATION, models.CountOrganizations, models.Organizations,
+		setting.AdminOrgPagingNum, "id ASC", ORGS)
 }

+ 3 - 18
routers/admin/repos.go

@@ -5,13 +5,12 @@
 package admin
 
 import (
-	"github.com/Unknwon/paginater"
-
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/context"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/setting"
+	"github.com/gogits/gogs/routers"
 )
 
 const (
@@ -23,22 +22,8 @@ func Repos(ctx *context.Context) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminRepositories"] = true
 
-	total := models.CountRepositories()
-	page := ctx.QueryInt("page")
-	if page <= 1 {
-		page = 1
-	}
-	ctx.Data["Page"] = paginater.New(int(total), setting.AdminRepoPagingNum, page, 5)
-
-	repos, err := models.RepositoriesWithUsers(page, setting.AdminRepoPagingNum)
-	if err != nil {
-		ctx.Handle(500, "RepositoriesWithUsers", err)
-		return
-	}
-	ctx.Data["Repos"] = repos
-
-	ctx.Data["Total"] = total
-	ctx.HTML(200, REPOS)
+	routers.RenderRepoSearch(ctx, models.CountRepositories, models.Repositories,
+		setting.AdminRepoPagingNum, "id ASC", REPOS)
 }
 
 func DeleteRepo(ctx *context.Context) {

+ 3 - 17
routers/admin/users.go

@@ -8,7 +8,6 @@ import (
 	"strings"
 
 	"github.com/Unknwon/com"
-	"github.com/Unknwon/paginater"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
@@ -17,6 +16,7 @@ import (
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/mailer"
 	"github.com/gogits/gogs/modules/setting"
+	"github.com/gogits/gogs/routers"
 )
 
 const (
@@ -30,22 +30,8 @@ func Users(ctx *context.Context) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
 
-	total := models.CountUsers()
-	page := ctx.QueryInt("page")
-	if page <= 1 {
-		page = 1
-	}
-	ctx.Data["Page"] = paginater.New(int(total), setting.AdminUserPagingNum, page, 5)
-
-	users, err := models.Users(page, setting.AdminUserPagingNum)
-	if err != nil {
-		ctx.Handle(500, "Users", err)
-		return
-	}
-	ctx.Data["Users"] = users
-
-	ctx.Data["Total"] = total
-	ctx.HTML(200, USERS)
+	routers.RenderUserSearch(ctx, models.USER_TYPE_INDIVIDUAL, models.CountUsers, models.Users,
+		setting.AdminUserPagingNum, "id ASC", USERS)
 }
 
 func NewUser(ctx *context.Context) {

+ 1 - 1
routers/api/v1/admin/orgs.go

@@ -27,7 +27,7 @@ func CreateOrg(ctx *context.Context, form api.CreateOrgOption) {
 		Website:     form.Website,
 		Location:    form.Location,
 		IsActive:    true,
-		Type:        models.ORGANIZATION,
+		Type:        models.USER_TYPE_ORGANIZATION,
 	}
 	if err := models.CreateOrganization(org, u); err != nil {
 		if models.IsErrUserAlreadyExist(err) ||

+ 12 - 12
routers/api/v1/repo/repo.go

@@ -21,21 +21,21 @@ import (
 
 // https://github.com/gogits/go-gogs-client/wiki/Repositories#search-repositories
 func Search(ctx *context.Context) {
-	opt := models.SearchOption{
-		Keyword: path.Base(ctx.Query("q")),
-		Uid:     com.StrTo(ctx.Query("uid")).MustInt64(),
-		Limit:   com.StrTo(ctx.Query("limit")).MustInt(),
+	opts := &models.SearchRepoOptions{
+		Keyword:  path.Base(ctx.Query("q")),
+		OwnerID:  com.StrTo(ctx.Query("uid")).MustInt64(),
+		PageSize: com.StrTo(ctx.Query("limit")).MustInt(),
 	}
-	if opt.Limit == 0 {
-		opt.Limit = 10
+	if opts.PageSize == 0 {
+		opts.PageSize = 10
 	}
 
 	// Check visibility.
-	if ctx.IsSigned && opt.Uid > 0 {
-		if ctx.User.Id == opt.Uid {
-			opt.Private = true
+	if ctx.IsSigned && opts.OwnerID > 0 {
+		if ctx.User.Id == opts.OwnerID {
+			opts.Private = true
 		} else {
-			u, err := models.GetUserByID(opt.Uid)
+			u, err := models.GetUserByID(opts.OwnerID)
 			if err != nil {
 				ctx.JSON(500, map[string]interface{}{
 					"ok":    false,
@@ -44,13 +44,13 @@ func Search(ctx *context.Context) {
 				return
 			}
 			if u.IsOrganization() && u.IsOwnedBy(ctx.User.Id) {
-				opt.Private = true
+				opts.Private = true
 			}
 			// FIXME: how about collaborators?
 		}
 	}
 
-	repos, err := models.SearchRepositoryByName(opt)
+	repos, _, err := models.SearchRepositoryByName(opts)
 	if err != nil {
 		ctx.JSON(500, map[string]interface{}{
 			"ok":    false,

+ 14 - 13
routers/api/v1/user/user.go

@@ -15,15 +15,16 @@ import (
 
 // https://github.com/gogits/go-gogs-client/wiki/Users#search-users
 func Search(ctx *context.Context) {
-	opt := models.SearchOption{
-		Keyword: ctx.Query("q"),
-		Limit:   com.StrTo(ctx.Query("limit")).MustInt(),
+	opts := &models.SearchUserOptions{
+		Keyword:  ctx.Query("q"),
+		Type:     models.USER_TYPE_INDIVIDUAL,
+		PageSize: com.StrTo(ctx.Query("limit")).MustInt(),
 	}
-	if opt.Limit == 0 {
-		opt.Limit = 10
+	if opts.PageSize == 0 {
+		opts.PageSize = 10
 	}
 
-	us, err := models.SearchUserByName(opt)
+	users, _, err := models.SearchUserByName(opts)
 	if err != nil {
 		ctx.JSON(500, map[string]interface{}{
 			"ok":    false,
@@ -32,16 +33,16 @@ func Search(ctx *context.Context) {
 		return
 	}
 
-	results := make([]*api.User, len(us))
-	for i := range us {
+	results := make([]*api.User, len(users))
+	for i := range users {
 		results[i] = &api.User{
-			ID:        us[i].Id,
-			UserName:  us[i].Name,
-			AvatarUrl: us[i].AvatarLink(),
-			FullName:  us[i].FullName,
+			ID:        users[i].Id,
+			UserName:  users[i].Name,
+			AvatarUrl: users[i].AvatarLink(),
+			FullName:  users[i].FullName,
 		}
 		if ctx.IsSigned {
-			results[i].Email = us[i].Email
+			results[i].Email = users[i].Email
 		}
 	}
 

+ 94 - 11
routers/home.go

@@ -19,6 +19,7 @@ import (
 const (
 	HOME          base.TplName = "home"
 	EXPLORE_REPOS base.TplName = "explore/repos"
+	EXPLORE_USERS base.TplName = "explore/users"
 )
 
 func Home(ctx *context.Context) {
@@ -43,23 +44,44 @@ func Home(ctx *context.Context) {
 	ctx.HTML(200, HOME)
 }
 
-func Explore(ctx *context.Context) {
-	ctx.Data["Title"] = ctx.Tr("explore")
-	ctx.Data["PageIsExplore"] = true
-	ctx.Data["PageIsExploreRepositories"] = true
-
+func RenderRepoSearch(ctx *context.Context,
+	counter func() int64, ranger func(int, int) ([]*models.Repository, error),
+	pagingNum int, orderBy string, tplName base.TplName) {
 	page := ctx.QueryInt("page")
 	if page <= 1 {
 		page = 1
 	}
 
-	ctx.Data["Page"] = paginater.New(int(models.CountPublicRepositories()), setting.ExplorePagingNum, page, 5)
+	var (
+		repos []*models.Repository
+		count int64
+		err   error
+	)
 
-	repos, err := models.GetRecentUpdatedRepositories(page)
-	if err != nil {
-		ctx.Handle(500, "GetRecentUpdatedRepositories", err)
-		return
+	keyword := ctx.Query("q")
+	if len(keyword) == 0 {
+		repos, err = ranger(page, pagingNum)
+		if err != nil {
+			ctx.Handle(500, "ranger", err)
+			return
+		}
+		count = counter()
+	} else {
+		repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
+			Keyword:  keyword,
+			OrderBy:  orderBy,
+			Page:     page,
+			PageSize: pagingNum,
+		})
+		if err != nil {
+			ctx.Handle(500, "SearchRepositoryByName", err)
+			return
+		}
 	}
+	ctx.Data["Keyword"] = keyword
+	ctx.Data["Total"] = count
+	ctx.Data["Page"] = paginater.New(int(count), pagingNum, page, 5)
+
 	for _, repo := range repos {
 		if err = repo.GetOwner(); err != nil {
 			ctx.Handle(500, "GetOwner", fmt.Errorf("%d: %v", repo.ID, err))
@@ -68,7 +90,68 @@ func Explore(ctx *context.Context) {
 	}
 	ctx.Data["Repos"] = repos
 
-	ctx.HTML(200, EXPLORE_REPOS)
+	ctx.HTML(200, tplName)
+}
+
+func ExploreRepos(ctx *context.Context) {
+	ctx.Data["Title"] = ctx.Tr("explore")
+	ctx.Data["PageIsExplore"] = true
+	ctx.Data["PageIsExploreRepositories"] = true
+
+	RenderRepoSearch(ctx, models.CountPublicRepositories, models.GetRecentUpdatedRepositories,
+		setting.ExplorePagingNum, "updated_unix DESC", EXPLORE_REPOS)
+}
+
+func RenderUserSearch(ctx *context.Context, userType models.UserType,
+	counter func() int64, ranger func(int, int) ([]*models.User, error),
+	pagingNum int, orderBy string, tplName base.TplName) {
+	page := ctx.QueryInt("page")
+	if page <= 1 {
+		page = 1
+	}
+
+	var (
+		users []*models.User
+		count int64
+		err   error
+	)
+
+	keyword := ctx.Query("q")
+	if len(keyword) == 0 {
+		users, err = ranger(page, pagingNum)
+		if err != nil {
+			ctx.Handle(500, "ranger", err)
+			return
+		}
+		count = counter()
+	} else {
+		users, count, err = models.SearchUserByName(&models.SearchUserOptions{
+			Keyword:  keyword,
+			Type:     userType,
+			OrderBy:  orderBy,
+			Page:     page,
+			PageSize: pagingNum,
+		})
+		if err != nil {
+			ctx.Handle(500, "SearchUserByName", err)
+			return
+		}
+	}
+	ctx.Data["Keyword"] = keyword
+	ctx.Data["Total"] = count
+	ctx.Data["Page"] = paginater.New(int(count), pagingNum, page, 5)
+	ctx.Data["Users"] = users
+
+	ctx.HTML(200, tplName)
+}
+
+func ExploreUsers(ctx *context.Context) {
+	ctx.Data["Title"] = ctx.Tr("explore")
+	ctx.Data["PageIsExplore"] = true
+	ctx.Data["PageIsExploreUsers"] = true
+
+	RenderUserSearch(ctx, models.USER_TYPE_INDIVIDUAL, models.CountUsers, models.Users,
+		setting.ExplorePagingNum, "updated_unix DESC", EXPLORE_USERS)
 }
 
 func NotFound(ctx *context.Context) {

+ 1 - 1
routers/org/org.go

@@ -33,7 +33,7 @@ func CreatePost(ctx *context.Context, form auth.CreateOrgForm) {
 	org := &models.User{
 		Name:     form.OrgName,
 		IsActive: true,
-		Type:     models.ORGANIZATION,
+		Type:     models.USER_TYPE_ORGANIZATION,
 	}
 
 	if err := models.CreateOrganization(org, ctx.User); err != nil {

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.9.5.0311
+0.9.6.0311

+ 23 - 0
templates/admin/base/page.tmpl

@@ -0,0 +1,23 @@
+	{{with .Page}}
+		{{if gt .TotalPages 1}}
+			<div class="center page buttons">
+				<div class="ui borderless pagination menu">
+					<a class="{{if .IsFirst}}disabled{{end}} item" href="{{$.Link}}?q={{$.Keyword}}"><i class="angle double left icon"></i> {{$.i18n.Tr "admin.first_page"}}</a>
+					<a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}&q={{$.Keyword}}"{{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}}&q={{$.Keyword}}"{{end}}>{{.Num}}</a>
+						{{end}}
+					{{end}}
+					<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}&q={{$.Keyword}}"{{end}}>
+						{{$.i18n.Tr "repo.issues.next"}}&nbsp;<i class="icon right arrow"></i>
+					</a>
+					<a class="{{if .IsLast}}disabled{{end}} item" href="{{$.Link}}?page={{.TotalPages}}&q={{$.Keyword}}">{{$.i18n.Tr "admin.last_page"}}&nbsp;<i class="angle double right icon"></i></a>
+				</div>
+			</div>
+		{{end}}
+	{{end}}

+ 6 - 0
templates/admin/base/search.tmpl

@@ -0,0 +1,6 @@
+<form class="ui form">
+	<div class="ui fluid action input">
+	  <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
+	  <button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
+	</div>
+</form>

+ 5 - 24
templates/admin/org/list.tmpl

@@ -8,6 +8,9 @@
 				<h4 class="ui top attached header">
 					{{.i18n.Tr "admin.orgs.org_manage_panel"}} ({{.i18n.Tr "admin.total" .Total}})
 				</h4>
+				<div class="ui attached segment">
+					{{template "admin/base/search" .}}
+				</div>
 				<div class="ui attached table segment">
 					<table class="ui very basic striped table">
 						<thead>
@@ -22,7 +25,7 @@
 							</tr>
 						</thead>
 						<tbody>
-							{{range .Orgs}}
+							{{range .Users}}
 								<tr>
 									<td>{{.Id}}</td>
 									<td><a href="{{.HomeLink}}">{{.Name}}</a></td>
@@ -37,29 +40,7 @@
 					</table>
 				</div>
 
-				{{with .Page}}
-					{{if gt .TotalPages 1}}
-						<div class="center page buttons">
-							<div class="ui borderless pagination menu">
-								<a class="{{if .IsFirst}}disabled{{end}} item" href="{{$.Link}}"><i class="angle double left icon"></i> {{$.i18n.Tr "admin.first_page"}}</a>
-								<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"}}&nbsp;<i class="icon right arrow"></i>
-								</a>
-								<a class="{{if .IsLast}}disabled{{end}} item" href="{{$.Link}}?page={{.TotalPages}}">{{$.i18n.Tr "admin.last_page"}}&nbsp;<i class="angle double right icon"></i></a>
-							</div>
-						</div>
-					{{end}}
-				{{end}}
+				{{template "admin/base/page" .}}
 			</div>
 		</div>
 	</div>

+ 4 - 23
templates/admin/repo/list.tmpl

@@ -8,6 +8,9 @@
 				<h4 class="ui top attached header">
 					{{.i18n.Tr "admin.repos.repo_manage_panel"}} ({{.i18n.Tr "admin.total" .Total}})
 				</h4>
+				<div class="ui attached segment">
+					{{template "admin/base/search" .}}
+				</div>
 				<div class="ui attached table segment">
 					<table class="ui very basic striped table">
 						<thead>
@@ -41,29 +44,7 @@
 					</table>
 				</div>
 
-				{{with .Page}}
-					{{if gt .TotalPages 1}}
-						<div class="center page buttons">
-							<div class="ui borderless pagination menu">
-								<a class="{{if .IsFirst}}disabled{{end}} item" href="{{$.Link}}"><i class="angle double left icon"></i> {{$.i18n.Tr "admin.first_page"}}</a>
-								<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"}}&nbsp;<i class="icon right arrow"></i>
-								</a>
-								<a class="{{if .IsLast}}disabled{{end}} item" href="{{$.Link}}?page={{.TotalPages}}">{{$.i18n.Tr "admin.last_page"}}&nbsp;<i class="angle double right icon"></i></a>
-							</div>
-						</div>
-					{{end}}
-				{{end}}
+				{{template "admin/base/page" .}}
 			</div>
 		</div>
 	</div>

+ 5 - 24
templates/admin/user/list.tmpl

@@ -8,9 +8,12 @@
 				<h4 class="ui top attached header">
 					{{.i18n.Tr "admin.users.user_manage_panel"}} ({{.i18n.Tr "admin.total" .Total}})
 					<div class="ui right">
-						<a class="ui blue tiny button" href="{{AppSubUrl}}/admin/users/new">{{.i18n.Tr "admin.users.new_account"}}</a>
+						<a class="ui black tiny button" href="{{AppSubUrl}}/admin/users/new">{{.i18n.Tr "admin.users.new_account"}}</a>
 					</div>
 				</h4>
+				<div class="ui attached segment">
+					{{template "admin/base/search" .}}
+				</div>
 				<div class="ui attached table segment">
 					<table class="ui very basic striped table">
 						<thead>
@@ -42,29 +45,7 @@
 					</table>
 				</div>
 
-				{{with .Page}}
-					{{if gt .TotalPages 1}}
-						<div class="center page buttons">
-							<div class="ui borderless pagination menu">
-								<a class="{{if .IsFirst}}disabled{{end}} item" href="{{$.Link}}"><i class="angle double left icon"></i> {{$.i18n.Tr "admin.first_page"}}</a>
-								<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"}}&nbsp;<i class="icon right arrow"></i>
-								</a>
-								<a class="{{if .IsLast}}disabled{{end}} item" href="{{$.Link}}?page={{.TotalPages}}">{{$.i18n.Tr "admin.last_page"}}&nbsp;<i class="angle double right icon"></i></a>
-							</div>
-						</div>
-					{{end}}
-				{{end}}
+				{{template "admin/base/page" .}}
 			</div>
 		</div>
 	</div>

+ 1 - 1
templates/base/head.tmpl

@@ -59,7 +59,7 @@
 									<a class="item{{if .PageIsHome}} active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a>
 								{{end}}
 
-								<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore">{{.i18n.Tr "explore"}}</a>
+								<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a>
 								{{/*<div class="item">
 									<div class="ui icon input">
 									<input class="searchbox" type="text" placeholder="{{.i18n.Tr "search_project"}}">

+ 6 - 3
templates/explore/navbar.tmpl

@@ -1,8 +1,11 @@
 <div class="four wide column">
-	<div class="ui vertical menu">
+	<div class="ui vertical menu navbar">
 		<div class="header item">{{.i18n.Tr "explore"}}</div>
-		<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore">
-			{{.i18n.Tr "explore.repos"}}
+		<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos">
+			<span class="octicon octicon-repo"></span> {{.i18n.Tr "explore.repos"}}
+		</a>
+		<a class="{{if .PageIsExploreUsers}}active{{end}} item" href="{{AppSubUrl}}/explore/users">
+			<span class="octicon octicon-person"></span> {{.i18n.Tr "explore.users"}}
 		</a>
 	</div>
 </div>

+ 21 - 0
templates/explore/page.tmpl

@@ -0,0 +1,21 @@
+{{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}}&q={{$.Keyword}}"{{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}}&q={{$.Keyword}}"{{end}}>{{.Num}}</a>
+					{{end}}
+				{{end}}
+				<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}&q={{$.Keyword}}"{{end}}>
+					{{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i>
+				</a>
+			</div>
+		</div>
+	{{end}}
+{{end}}

+ 2 - 22
templates/explore/repos.tmpl

@@ -4,29 +4,9 @@
 		<div class="ui grid">
 			{{template "explore/navbar" .}}
 			<div class="twelve wide column content">
+				{{template "explore/search" .}}
 				{{template "explore/repo_list" .}}
-
-				{{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}}
+				{{template "explore/page" .}}
 			</div>
 		</div>
 	</div>

+ 7 - 0
templates/explore/search.tmpl

@@ -0,0 +1,7 @@
+<form class="ui form">
+	<div class="ui fluid action input">
+	  <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
+	  <button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
+	</div>
+</form>
+<div class="ui divider"></div>

+ 35 - 0
templates/explore/users.tmpl

@@ -0,0 +1,35 @@
+{{template "base/head" .}}
+<div class="explore users">
+	<div class="ui container">
+		<div class="ui grid">
+			{{template "explore/navbar" .}}
+			<div class="twelve wide column content">
+				{{template "explore/search" .}}
+
+				<div class="ui user list">
+					{{range .Users}}
+						<div class="item">
+						  <img class="ui avatar image" src="{{.AvatarLink}}">
+						  <div class="content">
+						  	<span class="header"><a href="{{.HomeLink}}">{{.Name}}</a> {{.FullName}}</span>
+						    <div class="description">
+									{{if .Location}}
+										<i class="octicon octicon-location"></i> {{.Location}}
+									{{end}}
+									{{if and .Email $.IsSigned}}
+										<i class="octicon octicon-mail"></i>
+										<a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a>
+									{{end}}
+									<i class="octicon octicon-clock"></i> {{$.i18n.Tr "user.join_on"}} {{DateFmtShort .Created}}
+						    </div>
+						  </div>
+						</div>
+					{{end}}
+				</div>
+
+				{{template "explore/page" .}}
+			</div>
+		</div>
+	</div>
+</div>
+{{template "base/footer" .}}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác