mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-21 06:53:57 +01:00
feat(repo): support search repository by topic name (#4505)
* feat(repo): support search repository by topic name
This commit is contained in:
parent
7dd93b2441
commit
ea20adaa84
17 changed files with 134 additions and 26 deletions
|
@ -9,10 +9,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
api "code.gitea.io/sdk/gitea"
|
api "code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
|
func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
package integrations
|
package integrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
api "code.gitea.io/sdk/gitea"
|
api "code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
|
||||||
expectedResults
|
expectedResults
|
||||||
}{
|
}{
|
||||||
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
|
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
|
||||||
nil: {count: 17},
|
nil: {count: 19},
|
||||||
user: {count: 17},
|
user: {count: 19},
|
||||||
user2: {count: 17}},
|
user2: {count: 19}},
|
||||||
},
|
},
|
||||||
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
|
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
|
||||||
nil: {count: 10},
|
nil: {count: 10},
|
||||||
|
|
|
@ -9,3 +9,11 @@
|
||||||
-
|
-
|
||||||
repo_id: 1
|
repo_id: 1
|
||||||
topic_id: 3
|
topic_id: 3
|
||||||
|
|
||||||
|
-
|
||||||
|
repo_id: 33
|
||||||
|
topic_id: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
repo_id: 33
|
||||||
|
topic_id: 4
|
||||||
|
|
|
@ -407,3 +407,25 @@
|
||||||
lower_name: utf8
|
lower_name: utf8
|
||||||
name: utf8
|
name: utf8
|
||||||
is_private: false
|
is_private: false
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 34
|
||||||
|
owner_id: 21
|
||||||
|
lower_name: golang
|
||||||
|
name: golang
|
||||||
|
is_private: false
|
||||||
|
num_stars: 0
|
||||||
|
num_forks: 0
|
||||||
|
num_issues: 0
|
||||||
|
is_mirror: false
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 35
|
||||||
|
owner_id: 21
|
||||||
|
lower_name: graphql
|
||||||
|
name: graphql
|
||||||
|
is_private: false
|
||||||
|
num_stars: 0
|
||||||
|
num_forks: 0
|
||||||
|
num_issues: 0
|
||||||
|
is_mirror: false
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
-
|
-
|
||||||
id: 1
|
id: 1
|
||||||
name: golang
|
name: golang
|
||||||
repo_count: 1
|
repo_count: 2
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 2
|
id: 2
|
||||||
|
@ -11,3 +11,7 @@
|
||||||
- id: 3
|
- id: 3
|
||||||
name: SQL
|
name: SQL
|
||||||
repo_count: 1
|
repo_count: 1
|
||||||
|
|
||||||
|
- id: 4
|
||||||
|
name: graphql
|
||||||
|
repo_count: 1
|
||||||
|
|
|
@ -314,3 +314,18 @@
|
||||||
avatar_email: user20@example.com
|
avatar_email: user20@example.com
|
||||||
num_repos: 4
|
num_repos: 4
|
||||||
is_active: true
|
is_active: true
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 21
|
||||||
|
lower_name: user21
|
||||||
|
name: user21
|
||||||
|
full_name: User 21
|
||||||
|
email: user21@example.com
|
||||||
|
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
|
||||||
|
type: 0 # individual
|
||||||
|
salt: ZogKvWdyEx
|
||||||
|
is_admin: false
|
||||||
|
avatar: avatar21
|
||||||
|
avatar_email: user21@example.com
|
||||||
|
num_repos: 2
|
||||||
|
is_active: true
|
||||||
|
|
|
@ -131,6 +131,8 @@ type SearchRepoOptions struct {
|
||||||
// True -> include just mirrors
|
// True -> include just mirrors
|
||||||
// False -> include just non-mirrors
|
// False -> include just non-mirrors
|
||||||
Mirror util.OptionalBool
|
Mirror util.OptionalBool
|
||||||
|
// only search topic name
|
||||||
|
TopicOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//SearchOrderBy is used to sort the result
|
//SearchOrderBy is used to sort the result
|
||||||
|
@ -184,7 +186,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
|
||||||
|
|
||||||
if opts.Collaborate != util.OptionalBoolFalse {
|
if opts.Collaborate != util.OptionalBoolFalse {
|
||||||
collaborateCond := builder.And(
|
collaborateCond := builder.And(
|
||||||
builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
|
builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
|
||||||
builder.Neq{"owner_id": opts.OwnerID})
|
builder.Neq{"owner_id": opts.OwnerID})
|
||||||
if !opts.Private {
|
if !opts.Private {
|
||||||
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
|
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
|
||||||
|
@ -202,7 +204,14 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Keyword != "" {
|
if opts.Keyword != "" {
|
||||||
cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
|
var keywordCond = builder.NewCond()
|
||||||
|
if opts.TopicOnly {
|
||||||
|
keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
|
||||||
|
} else {
|
||||||
|
keywordCond = keywordCond.Or(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
|
||||||
|
keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
|
||||||
|
}
|
||||||
|
cond = cond.And(keywordCond)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Fork != util.OptionalBoolNone {
|
if opts.Fork != util.OptionalBoolNone {
|
||||||
|
@ -224,9 +233,15 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
|
||||||
sess.Join("INNER", "star", "star.repo_id = repository.id")
|
sess.Join("INNER", "star", "star.repo_id = repository.id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Keyword != "" {
|
||||||
|
sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
|
||||||
|
sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
|
||||||
|
}
|
||||||
|
|
||||||
count, err := sess.
|
count, err := sess.
|
||||||
Where(cond).
|
Where(cond).
|
||||||
Count(new(Repository))
|
Count(new(Repository))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("Count: %v", err)
|
return nil, 0, fmt.Errorf("Count: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -236,11 +251,23 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
|
||||||
sess.Join("INNER", "star", "star.repo_id = repository.id")
|
sess.Join("INNER", "star", "star.repo_id = repository.id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Keyword != "" {
|
||||||
|
sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
|
||||||
|
sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Keyword != "" {
|
||||||
|
sess.Select("repository.*")
|
||||||
|
sess.GroupBy("repository.id")
|
||||||
|
sess.OrderBy("repository." + opts.OrderBy.String())
|
||||||
|
} else {
|
||||||
|
sess.OrderBy(opts.OrderBy.String())
|
||||||
|
}
|
||||||
|
|
||||||
repos := make(RepositoryList, 0, opts.PageSize)
|
repos := make(RepositoryList, 0, opts.PageSize)
|
||||||
if err = sess.
|
if err = sess.
|
||||||
Where(cond).
|
Where(cond).
|
||||||
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
|
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
|
||||||
OrderBy(opts.OrderBy.String()).
|
|
||||||
Find(&repos); err != nil {
|
Find(&repos); err != nil {
|
||||||
return nil, 0, fmt.Errorf("Repo: %v", err)
|
return nil, 0, fmt.Errorf("Repo: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
|
||||||
count: 14},
|
count: 14},
|
||||||
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
||||||
count: 17},
|
count: 19},
|
||||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||||
count: 21},
|
count: 23},
|
||||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||||
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||||
count: 13},
|
count: 13},
|
||||||
|
@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
|
||||||
count: 11},
|
count: 11},
|
||||||
{name: "AllPublic/PublicRepositoriesOfOrganization",
|
{name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
||||||
count: 17},
|
count: 19},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
@ -222,3 +222,28 @@ func TestSearchRepositoryByName(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchRepositoryByTopicName(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
opts *SearchRepoOptions
|
||||||
|
count int
|
||||||
|
}{
|
||||||
|
{name: "AllPublic/SearchPublicRepositoriesFromTopicAndName",
|
||||||
|
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"},
|
||||||
|
count: 2},
|
||||||
|
{name: "AllPublic/OnlySearchPublicRepositoriesFromTopic",
|
||||||
|
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true},
|
||||||
|
count: 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
_, count, err := SearchRepositoryByName(testCase.opts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(testCase.count), count)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,14 +18,14 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Unknwon/com"
|
|
||||||
"github.com/go-xorm/xorm"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/Unknwon/com"
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -15,7 +15,7 @@ func TestAddTopic(t *testing.T) {
|
||||||
|
|
||||||
topics, err := FindTopics(&FindTopicOptions{})
|
topics, err := FindTopics(&FindTopicOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 3, len(topics))
|
assert.EqualValues(t, 4, len(topics))
|
||||||
|
|
||||||
topics, err = FindTopics(&FindTopicOptions{
|
topics, err = FindTopics(&FindTopicOptions{
|
||||||
Limit: 2,
|
Limit: 2,
|
||||||
|
@ -32,7 +32,7 @@ func TestAddTopic(t *testing.T) {
|
||||||
assert.NoError(t, SaveTopics(2, "golang"))
|
assert.NoError(t, SaveTopics(2, "golang"))
|
||||||
topics, err = FindTopics(&FindTopicOptions{})
|
topics, err = FindTopics(&FindTopicOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 3, len(topics))
|
assert.EqualValues(t, 4, len(topics))
|
||||||
|
|
||||||
topics, err = FindTopics(&FindTopicOptions{
|
topics, err = FindTopics(&FindTopicOptions{
|
||||||
RepoID: 2,
|
RepoID: 2,
|
||||||
|
@ -47,7 +47,7 @@ func TestAddTopic(t *testing.T) {
|
||||||
|
|
||||||
topics, err = FindTopics(&FindTopicOptions{})
|
topics, err = FindTopics(&FindTopicOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 4, len(topics))
|
assert.EqualValues(t, 5, len(topics))
|
||||||
|
|
||||||
topics, err = FindTopics(&FindTopicOptions{
|
topics, err = FindTopics(&FindTopicOptions{
|
||||||
RepoID: 2,
|
RepoID: 2,
|
||||||
|
|
|
@ -77,13 +77,13 @@ func TestSearchUsers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
|
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
|
||||||
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20})
|
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
|
||||||
|
|
||||||
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
|
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
|
||||||
[]int64{9})
|
[]int64{9})
|
||||||
|
|
||||||
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
|
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
|
||||||
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20})
|
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
|
||||||
|
|
||||||
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
|
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
|
||||||
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
|
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
|
||||||
|
|
|
@ -91,6 +91,7 @@ func Search(ctx *context.APIContext) {
|
||||||
OwnerID: ctx.QueryInt64("uid"),
|
OwnerID: ctx.QueryInt64("uid"),
|
||||||
Page: ctx.QueryInt("page"),
|
Page: ctx.QueryInt("page"),
|
||||||
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
|
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
|
||||||
|
TopicOnly: ctx.QueryBool("topic"),
|
||||||
Collaborate: util.OptionalBoolNone,
|
Collaborate: util.OptionalBoolNone,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
keyword := strings.Trim(ctx.Query("q"), " ")
|
keyword := strings.Trim(ctx.Query("q"), " ")
|
||||||
|
topicOnly := ctx.QueryBool("topic")
|
||||||
|
|
||||||
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
|
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
|
||||||
Page: page,
|
Page: page,
|
||||||
|
@ -131,6 +132,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
||||||
Keyword: keyword,
|
Keyword: keyword,
|
||||||
OwnerID: opts.OwnerID,
|
OwnerID: opts.OwnerID,
|
||||||
AllPublic: true,
|
AllPublic: true,
|
||||||
|
TopicOnly: topicOnly,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SearchRepositoryByName", err)
|
ctx.ServerError("SearchRepositoryByName", err)
|
||||||
|
|
|
@ -105,6 +105,8 @@ func Profile(ctx *context.Context) {
|
||||||
page = 1
|
page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
topicOnly := ctx.QueryBool("topic")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
repos []*models.Repository
|
repos []*models.Repository
|
||||||
count int64
|
count int64
|
||||||
|
@ -174,6 +176,7 @@ func Profile(ctx *context.Context) {
|
||||||
PageSize: setting.UI.User.RepoPagingNum,
|
PageSize: setting.UI.User.RepoPagingNum,
|
||||||
Starred: true,
|
Starred: true,
|
||||||
Collaborate: util.OptionalBoolFalse,
|
Collaborate: util.OptionalBoolFalse,
|
||||||
|
TopicOnly: topicOnly,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SearchRepositoryByName", err)
|
ctx.ServerError("SearchRepositoryByName", err)
|
||||||
|
@ -217,6 +220,7 @@ func Profile(ctx *context.Context) {
|
||||||
IsProfile: true,
|
IsProfile: true,
|
||||||
PageSize: setting.UI.User.RepoPagingNum,
|
PageSize: setting.UI.User.RepoPagingNum,
|
||||||
Collaborate: util.OptionalBoolFalse,
|
Collaborate: util.OptionalBoolFalse,
|
||||||
|
TopicOnly: topicOnly,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SearchRepositoryByName", err)
|
ctx.ServerError("SearchRepositoryByName", err)
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
{{if .Topics }}
|
{{if .Topics }}
|
||||||
<div>
|
<div>
|
||||||
{{range .Topics}}
|
{{range .Topics}}
|
||||||
{{if ne . "" }}<div class="ui green basic label topic">{{.}}</div>{{end}}
|
{{if ne . "" }}<a href="/explore/repos?q={{.}}&topic=1"><div class="ui green basic label topic">{{.}}</div></a>{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui repo-topic" id="repo-topic">
|
<div class="ui repo-topic" id="repo-topic">
|
||||||
{{range .Topics}}<div class="ui green basic label topic" style="cursor:pointer;">{{.Name}}</div>{{end}}
|
{{range .Topics}}<a class="ui green basic label topic" style="cursor:pointer;" href="/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
|
||||||
{{if .IsRepositoryAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
|
{{if .IsRepositoryAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;" href="/explore/repos?q={{.Name}}&topic=1">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{if .IsRepositoryAdmin}}
|
{{if .IsRepositoryAdmin}}
|
||||||
<div class="ui repo-topic-edit grid form segment error" id="topic_edit" >
|
<div class="ui repo-topic-edit grid form segment error" id="topic_edit" >
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<div class="ui fluid multiple search selection dropdown">
|
<div class="ui fluid multiple search selection dropdown">
|
||||||
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Add $i 1) (len $.Topics)}},{{end}}{{end}}">
|
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Add $i 1) (len $.Topics)}},{{end}}{{end}}">
|
||||||
{{range .Topics}}
|
{{range .Topics}}
|
||||||
<a class="ui green basic label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important;">{{.Name}}<i class="delete icon"></i></a>
|
<a class="ui green basic label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important;" href="/explore/repos?q={{.Name}}&topic=1">{{.Name}}<i class="delete icon"></i></a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="text"></div>
|
<div class="text"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue