* feat: support search bar on star tab of user profile. * fix: update testing. * fix: Using loadAttributes * fix: remove empty line. * remove LOWER Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>tags/v1.21.12.1
| @@ -1778,13 +1778,15 @@ type SearchRepoOptions struct { | |||||
| Searcher *User //ID of the person who's seeking | Searcher *User //ID of the person who's seeking | ||||
| OrderBy string | OrderBy string | ||||
| Private bool // Include private repositories in results | Private bool // Include private repositories in results | ||||
| Starred bool | |||||
| Page int | Page int | ||||
| PageSize int // Can be smaller than or equal to setting.ExplorePagingNum | PageSize int // Can be smaller than or equal to setting.ExplorePagingNum | ||||
| } | } | ||||
| // SearchRepositoryByName takes keyword and part of repository name to search, | // SearchRepositoryByName takes keyword and part of repository name to search, | ||||
| // it returns results in given range and number of total results. | // it returns results in given range and number of total results. | ||||
| func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, _ int64, _ error) { | |||||
| func SearchRepositoryByName(opts *SearchRepoOptions) (repos RepositoryList, _ int64, _ error) { | |||||
| var sess *xorm.Session | |||||
| if len(opts.Keyword) == 0 { | if len(opts.Keyword) == 0 { | ||||
| return repos, 0, nil | return repos, 0, nil | ||||
| } | } | ||||
| @@ -1796,9 +1798,17 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, _ int | |||||
| repos = make([]*Repository, 0, opts.PageSize) | repos = make([]*Repository, 0, opts.PageSize) | ||||
| if opts.Starred && opts.OwnerID > 0 { | |||||
| sess = x. | |||||
| Join("INNER", "star", "star.repo_id = repository.id"). | |||||
| Where("star.uid = ?", opts.OwnerID). | |||||
| And("lower_name LIKE ?", "%"+opts.Keyword+"%") | |||||
| } else { | |||||
| sess = x.Where("lower_name LIKE ?", "%"+opts.Keyword+"%") | |||||
| } | |||||
| // Append conditions | // Append conditions | ||||
| sess := x.Where("LOWER(lower_name) LIKE ?", "%"+opts.Keyword+"%") | |||||
| if opts.OwnerID > 0 { | |||||
| if !opts.Starred && opts.OwnerID > 0 { | |||||
| sess.And("owner_id = ?", opts.OwnerID) | sess.And("owner_id = ?", opts.OwnerID) | ||||
| } | } | ||||
| if !opts.Private { | if !opts.Private { | ||||
| @@ -1831,10 +1841,20 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, _ int | |||||
| return nil, 0, fmt.Errorf("Count: %v", err) | return nil, 0, fmt.Errorf("Count: %v", err) | ||||
| } | } | ||||
| return repos, count, sess. | |||||
| if err = sess. | |||||
| Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). | Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). | ||||
| OrderBy(opts.OrderBy). | OrderBy(opts.OrderBy). | ||||
| Find(&repos) | |||||
| Find(&repos); err != nil { | |||||
| return nil, 0, fmt.Errorf("Repo: %v", err) | |||||
| } | |||||
| if opts.Starred { | |||||
| if err = repos.loadAttributes(x); err != nil { | |||||
| return nil, 0, fmt.Errorf("LoadAttributes: %v", err) | |||||
| } | |||||
| } | |||||
| return repos, count, nil | |||||
| } | } | ||||
| // DeleteRepositoryArchives deletes all repositories' archives. | // DeleteRepositoryArchives deletes all repositories' archives. | ||||
| @@ -73,12 +73,12 @@ func (repo *Repository) GetStargazers(page int) ([]*User, error) { | |||||
| // GetStarredRepos returns the repos the user starred. | // GetStarredRepos returns the repos the user starred. | ||||
| func (u *User) GetStarredRepos(private bool, page, pageSize int, orderBy string) (repos []*Repository, err error) { | func (u *User) GetStarredRepos(private bool, page, pageSize int, orderBy string) (repos []*Repository, err error) { | ||||
| if len(orderBy) == 0 { | if len(orderBy) == 0 { | ||||
| orderBy = "star.id" | |||||
| orderBy = "updated_unix DESC" | |||||
| } | } | ||||
| sess := x. | sess := x. | ||||
| Join("INNER", "star", "star.repo_id = repository.id"). | Join("INNER", "star", "star.repo_id = repository.id"). | ||||
| Where("star.uid = ?", u.ID). | Where("star.uid = ?", u.ID). | ||||
| Desc(orderBy) | |||||
| OrderBy(orderBy) | |||||
| if !private { | if !private { | ||||
| sess = sess.And("is_private = ?", false) | sess = sess.And("is_private = ?", false) | ||||
| @@ -61,8 +61,8 @@ func TestUser_GetStarredRepos(t *testing.T) { | |||||
| starred, err = user.GetStarredRepos(true, 1, 10, "") | starred, err = user.GetStarredRepos(true, 1, 10, "") | ||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.Len(t, starred, 2) | assert.Len(t, starred, 2) | ||||
| assert.Equal(t, int64(4), starred[0].ID) | |||||
| assert.Equal(t, int64(2), starred[1].ID) | |||||
| assert.Equal(t, int64(2), starred[0].ID) | |||||
| assert.Equal(t, int64(4), starred[1].ID) | |||||
| } | } | ||||
| func TestUser_GetStarredRepos2(t *testing.T) { | func TestUser_GetStarredRepos2(t *testing.T) { | ||||
| @@ -90,6 +90,44 @@ func Profile(ctx *context.Context) { | |||||
| tab := ctx.Query("tab") | tab := ctx.Query("tab") | ||||
| ctx.Data["TabName"] = tab | ctx.Data["TabName"] = tab | ||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| var ( | |||||
| repos []*models.Repository | |||||
| count int64 | |||||
| orderBy string | |||||
| ) | |||||
| ctx.Data["SortType"] = ctx.Query("sort") | |||||
| switch ctx.Query("sort") { | |||||
| case "newest": | |||||
| orderBy = "created_unix DESC" | |||||
| case "oldest": | |||||
| orderBy = "created_unix ASC" | |||||
| case "recentupdate": | |||||
| orderBy = "updated_unix DESC" | |||||
| case "leastupdate": | |||||
| orderBy = "updated_unix ASC" | |||||
| case "reversealphabetically": | |||||
| orderBy = "name DESC" | |||||
| case "alphabetically": | |||||
| orderBy = "name ASC" | |||||
| default: | |||||
| ctx.Data["SortType"] = "recentupdate" | |||||
| orderBy = "updated_unix DESC" | |||||
| } | |||||
| // set default sort value if sort is empty. | |||||
| if ctx.Query("sort") == "" { | |||||
| ctx.Data["SortType"] = "recentupdate" | |||||
| } | |||||
| keyword := strings.Trim(ctx.Query("q"), " ") | |||||
| ctx.Data["Keyword"] = keyword | |||||
| switch tab { | switch tab { | ||||
| case "activity": | case "activity": | ||||
| retrieveFeeds(ctx, ctxUser, -1, 0, !showPrivate) | retrieveFeeds(ctx, ctxUser, -1, 0, !showPrivate) | ||||
| @@ -97,66 +135,39 @@ func Profile(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| case "stars": | case "stars": | ||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| repos, err := ctxUser.GetStarredRepos(showPrivate, page, setting.UI.User.RepoPagingNum, "") | |||||
| if err != nil { | |||||
| ctx.Handle(500, "GetStarredRepos", err) | |||||
| return | |||||
| } | |||||
| ctx.Data["PageIsProfileStarList"] = true | |||||
| if len(keyword) == 0 { | |||||
| repos, err = ctxUser.GetStarredRepos(showPrivate, page, setting.UI.User.RepoPagingNum, orderBy) | |||||
| if err != nil { | |||||
| ctx.Handle(500, "GetStarredRepos", err) | |||||
| return | |||||
| } | |||||
| counts, err := ctxUser.GetStarredRepoCount(showPrivate) | |||||
| if err != nil { | |||||
| ctx.Handle(500, "GetStarredRepoCount", err) | |||||
| return | |||||
| count, err = ctxUser.GetStarredRepoCount(showPrivate) | |||||
| if err != nil { | |||||
| ctx.Handle(500, "GetStarredRepoCount", err) | |||||
| return | |||||
| } | |||||
| } else { | |||||
| repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ | |||||
| Keyword: keyword, | |||||
| OwnerID: ctxUser.ID, | |||||
| OrderBy: orderBy, | |||||
| Private: showPrivate, | |||||
| Page: page, | |||||
| PageSize: setting.UI.User.RepoPagingNum, | |||||
| Starred: true, | |||||
| }) | |||||
| if err != nil { | |||||
| ctx.Handle(500, "SearchRepositoryByName", err) | |||||
| return | |||||
| } | |||||
| } | } | ||||
| ctx.Data["Repos"] = repos | ctx.Data["Repos"] = repos | ||||
| ctx.Data["Page"] = paginater.New(int(counts), setting.UI.User.RepoPagingNum, page, 5) | |||||
| ctx.Data["Total"] = int(counts) | |||||
| ctx.Data["Tabs"] = "stars" | |||||
| ctx.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5) | |||||
| ctx.Data["Total"] = count | |||||
| default: | default: | ||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| var ( | |||||
| repos []*models.Repository | |||||
| count int64 | |||||
| err error | |||||
| orderBy string | |||||
| ) | |||||
| ctx.Data["SortType"] = ctx.Query("sort") | |||||
| switch ctx.Query("sort") { | |||||
| case "newest": | |||||
| orderBy = "created_unix DESC" | |||||
| case "oldest": | |||||
| orderBy = "created_unix ASC" | |||||
| case "recentupdate": | |||||
| orderBy = "updated_unix DESC" | |||||
| case "leastupdate": | |||||
| orderBy = "updated_unix ASC" | |||||
| case "reversealphabetically": | |||||
| orderBy = "name DESC" | |||||
| case "alphabetically": | |||||
| orderBy = "name ASC" | |||||
| default: | |||||
| ctx.Data["SortType"] = "recentupdate" | |||||
| orderBy = "updated_unix DESC" | |||||
| } | |||||
| // set default sort value if sort is empty. | |||||
| if ctx.Query("sort") == "" { | |||||
| ctx.Data["SortType"] = "recentupdate" | |||||
| } | |||||
| keyword := strings.Trim(ctx.Query("q"), " ") | |||||
| ctx.Data["Keyword"] = keyword | |||||
| if len(keyword) == 0 { | if len(keyword) == 0 { | ||||
| var total int | var total int | ||||
| repos, err = models.GetUserRepositories(ctxUser.ID, showPrivate, page, setting.UI.User.RepoPagingNum, orderBy) | repos, err = models.GetUserRepositories(ctxUser.ID, showPrivate, page, setting.UI.User.RepoPagingNum, orderBy) | ||||
| @@ -2,21 +2,21 @@ | |||||
| {{if gt .TotalPages 1}} | {{if gt .TotalPages 1}} | ||||
| <div class="center page buttons"> | <div class="center page buttons"> | ||||
| <div class="ui borderless pagination menu"> | <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}}&tab={{$.Tabs}}"{{end}}> | |||||
| <a class="{{if .IsFirst}}disabled{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&tab={{$.TabName}}"><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}}&tab={{$.TabName}}"{{end}}> | |||||
| <i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}} | <i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}} | ||||
| </a> | </a> | ||||
| {{range .Pages}} | {{range .Pages}} | ||||
| {{if eq .Num -1}} | {{if eq .Num -1}} | ||||
| <a class="disabled item">...</a> | <a class="disabled item">...</a> | ||||
| {{else}} | {{else}} | ||||
| <a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}&q={{$.Keyword}}&tab={{$.Tabs}}"{{end}}>{{.Num}}</a> | |||||
| <a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}&q={{$.Keyword}}&tab={{$.TabName}}"{{end}}>{{.Num}}</a> | |||||
| {{end}} | {{end}} | ||||
| {{end}} | {{end}} | ||||
| <a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}&q={{$.Keyword}}&tab={{$.Tabs}}"{{end}}> | |||||
| <a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}&q={{$.Keyword}}&tab={{$.TabName}}"{{end}}> | |||||
| {{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i> | {{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i> | ||||
| </a> | </a> | ||||
| <a class="{{if .IsLast}}disabled{{end}} item" href="{{$.Link}}?page={{.TotalPages}}&q={{$.Keyword}}&tab={{$.Tabs}}">{{$.i18n.Tr "admin.last_page"}} <i class="angle double right icon"></i></a> | |||||
| <a class="{{if .IsLast}}disabled{{end}} item" href="{{$.Link}}?page={{.TotalPages}}&q={{$.Keyword}}&tab={{$.TabName}}">{{$.i18n.Tr "admin.last_page"}} <i class="angle double right icon"></i></a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{end}} | {{end}} | ||||
| @@ -2,7 +2,7 @@ | |||||
| {{range .Repos}} | {{range .Repos}} | ||||
| <div class="item"> | <div class="item"> | ||||
| <div class="ui header"> | <div class="ui header"> | ||||
| <a class="name" href="{{AppSubUrl}}/{{if .Owner}}{{.Owner.Name}}{{else if $.Org}}{{$.Org.Name}}{{else}}{{$.Owner.Name}}{{end}}/{{.Name}}">{{if $.PageIsExplore}}{{.Owner.Name}} / {{end}}{{.Name}}</a> | |||||
| <a class="name" href="{{AppSubUrl}}/{{if .Owner}}{{.Owner.Name}}{{else if $.Org}}{{$.Org.Name}}{{else}}{{$.Owner.Name}}{{end}}/{{.Name}}">{{if or $.PageIsExplore $.PageIsProfileStarList }}{{.Owner.Name}} / {{end}}{{.Name}}</a> | |||||
| {{if .IsPrivate}} | {{if .IsPrivate}} | ||||
| <span class="text gold"><i class="octicon octicon-lock"></i></span> | <span class="text gold"><i class="octicon octicon-lock"></i></span> | ||||
| {{else if .IsFork}} | {{else if .IsFork}} | ||||
| @@ -6,18 +6,19 @@ | |||||
| <i class="dropdown icon"></i> | <i class="dropdown icon"></i> | ||||
| </span> | </span> | ||||
| <div class="menu"> | <div class="menu"> | ||||
| <a class="{{if or (eq .SortType "newest") (not .SortType)}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> | |||||
| <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> | |||||
| <a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> | |||||
| <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> | |||||
| <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> | |||||
| <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> | |||||
| <a class="{{if or (eq .SortType "newest") (not .SortType)}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> | |||||
| <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> | |||||
| <a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> | |||||
| <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> | |||||
| <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> | |||||
| <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <form class="ui form" style="max-width: 90%"> | <form class="ui form" style="max-width: 90%"> | ||||
| <div class="ui fluid action input"> | <div class="ui fluid action input"> | ||||
| <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | ||||
| <input type="hidden" name="tab" value="{{$.TabName}}"> | |||||
| <button class="ui blue button">{{.i18n.Tr "explore.search"}}</button> | <button class="ui blue button">{{.i18n.Tr "explore.search"}}</button> | ||||
| </div> | </div> | ||||
| </form> | </form> | ||||
| @@ -92,6 +92,7 @@ | |||||
| </div> | </div> | ||||
| {{else if eq .TabName "stars"}} | {{else if eq .TabName "stars"}} | ||||
| <div class="stars"> | <div class="stars"> | ||||
| {{template "explore/search" .}} | |||||
| {{template "explore/repo_list" .}} | {{template "explore/repo_list" .}} | ||||
| {{template "base/paginate" .}} | {{template "base/paginate" .}} | ||||
| </div> | </div> | ||||