Merge branch 'main' into lunny/refactor_getpatch

This commit is contained in:
Lunny Xiao 2024-12-11 15:40:10 -08:00
commit 72f6e28f7f
54 changed files with 797 additions and 853 deletions

8
go.mod
View File

@ -121,13 +121,13 @@ require (
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.31.0
golang.org/x/image v0.21.0 golang.org/x/image v0.21.0
golang.org/x/net v0.30.0 golang.org/x/net v0.30.0
golang.org/x/oauth2 v0.23.0 golang.org/x/oauth2 v0.23.0
golang.org/x/sync v0.8.0 golang.org/x/sync v0.10.0
golang.org/x/sys v0.26.0 golang.org/x/sys v0.28.0
golang.org/x/text v0.19.0 golang.org/x/text v0.21.0
golang.org/x/tools v0.26.0 golang.org/x/tools v0.26.0
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1 google.golang.org/protobuf v1.35.1

15
go.sum
View File

@ -893,8 +893,9 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
@ -946,8 +947,9 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -982,8 +984,9 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -996,8 +999,9 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -1009,8 +1013,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -23,7 +23,7 @@ import (
const ( const (
issueIndexerAnalyzer = "issueIndexer" issueIndexerAnalyzer = "issueIndexer"
issueIndexerDocType = "issueIndexerDocType" issueIndexerDocType = "issueIndexerDocType"
issueIndexerLatestVersion = 4 issueIndexerLatestVersion = 5
) )
const unicodeNormalizeName = "unicodeNormalize" const unicodeNormalizeName = "unicodeNormalize"
@ -75,6 +75,7 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) {
docMapping.AddFieldMappingsAt("is_pull", boolFieldMapping) docMapping.AddFieldMappingsAt("is_pull", boolFieldMapping)
docMapping.AddFieldMappingsAt("is_closed", boolFieldMapping) docMapping.AddFieldMappingsAt("is_closed", boolFieldMapping)
docMapping.AddFieldMappingsAt("is_archived", boolFieldMapping)
docMapping.AddFieldMappingsAt("label_ids", numberFieldMapping) docMapping.AddFieldMappingsAt("label_ids", numberFieldMapping)
docMapping.AddFieldMappingsAt("no_label", boolFieldMapping) docMapping.AddFieldMappingsAt("no_label", boolFieldMapping)
docMapping.AddFieldMappingsAt("milestone_id", numberFieldMapping) docMapping.AddFieldMappingsAt("milestone_id", numberFieldMapping)
@ -185,6 +186,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
if options.IsClosed.Has() { if options.IsClosed.Has() {
queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.Value(), "is_closed")) queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.Value(), "is_closed"))
} }
if options.IsArchived.Has() {
queries = append(queries, inner_bleve.BoolFieldQuery(options.IsArchived.Value(), "is_archived"))
}
if options.NoLabelOnly { if options.NoLabelOnly {
queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_label")) queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_label"))

View File

@ -72,7 +72,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
UpdatedAfterUnix: options.UpdatedAfterUnix.Value(), UpdatedAfterUnix: options.UpdatedAfterUnix.Value(),
UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(), UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(),
PriorityRepoID: 0, PriorityRepoID: 0,
IsArchived: optional.None[bool](), IsArchived: options.IsArchived,
Org: nil, Org: nil,
Team: nil, Team: nil,
User: nil, User: nil,

View File

@ -11,11 +11,12 @@ import (
func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions { func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions {
searchOpt := &SearchOptions{ searchOpt := &SearchOptions{
Keyword: keyword, Keyword: keyword,
RepoIDs: opts.RepoIDs, RepoIDs: opts.RepoIDs,
AllPublic: opts.AllPublic, AllPublic: opts.AllPublic,
IsPull: opts.IsPull, IsPull: opts.IsPull,
IsClosed: opts.IsClosed, IsClosed: opts.IsClosed,
IsArchived: opts.IsArchived,
} }
if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 { if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 {

View File

@ -18,7 +18,7 @@ import (
) )
const ( const (
issueIndexerLatestVersion = 1 issueIndexerLatestVersion = 2
// multi-match-types, currently only 2 types are used // multi-match-types, currently only 2 types are used
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
esMultiMatchTypeBestFields = "best_fields" esMultiMatchTypeBestFields = "best_fields"
@ -58,6 +58,7 @@ const (
"is_pull": { "type": "boolean", "index": true }, "is_pull": { "type": "boolean", "index": true },
"is_closed": { "type": "boolean", "index": true }, "is_closed": { "type": "boolean", "index": true },
"is_archived": { "type": "boolean", "index": true },
"label_ids": { "type": "integer", "index": true }, "label_ids": { "type": "integer", "index": true },
"no_label": { "type": "boolean", "index": true }, "no_label": { "type": "boolean", "index": true },
"milestone_id": { "type": "integer", "index": true }, "milestone_id": { "type": "integer", "index": true },
@ -168,6 +169,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
if options.IsClosed.Has() { if options.IsClosed.Has() {
query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.Value())) query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.Value()))
} }
if options.IsArchived.Has() {
query.Must(elastic.NewTermQuery("is_archived", options.IsArchived.Value()))
}
if options.NoLabelOnly { if options.NoLabelOnly {
query.Must(elastic.NewTermQuery("no_label", true)) query.Must(elastic.NewTermQuery("no_label", true))

View File

@ -37,6 +37,7 @@ func TestDBSearchIssues(t *testing.T) {
t.Run("search issues by ID", searchIssueByID) t.Run("search issues by ID", searchIssueByID)
t.Run("search issues is pr", searchIssueIsPull) t.Run("search issues is pr", searchIssueIsPull)
t.Run("search issues is closed", searchIssueIsClosed) t.Run("search issues is closed", searchIssueIsClosed)
t.Run("search issues is archived", searchIssueIsArchived)
t.Run("search issues by milestone", searchIssueByMilestoneID) t.Run("search issues by milestone", searchIssueByMilestoneID)
t.Run("search issues by label", searchIssueByLabelID) t.Run("search issues by label", searchIssueByLabelID)
t.Run("search issues by time", searchIssueByTime) t.Run("search issues by time", searchIssueByTime)
@ -298,6 +299,33 @@ func searchIssueIsClosed(t *testing.T) {
} }
} }
func searchIssueIsArchived(t *testing.T) {
tests := []struct {
opts SearchOptions
expectedIDs []int64
}{
{
SearchOptions{
IsArchived: optional.Some(false),
},
[]int64{22, 21, 17, 16, 15, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
},
{
SearchOptions{
IsArchived: optional.Some(true),
},
[]int64{14},
},
}
for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, test.expectedIDs, issueIDs)
}
}
func searchIssueByMilestoneID(t *testing.T) { func searchIssueByMilestoneID(t *testing.T) {
tests := []struct { tests := []struct {
opts SearchOptions opts SearchOptions

View File

@ -25,6 +25,7 @@ type IndexerData struct {
// Fields used for filtering // Fields used for filtering
IsPull bool `json:"is_pull"` IsPull bool `json:"is_pull"`
IsClosed bool `json:"is_closed"` IsClosed bool `json:"is_closed"`
IsArchived bool `json:"is_archived"`
LabelIDs []int64 `json:"label_ids"` LabelIDs []int64 `json:"label_ids"`
NoLabel bool `json:"no_label"` // True if LabelIDs is empty NoLabel bool `json:"no_label"` // True if LabelIDs is empty
MilestoneID int64 `json:"milestone_id"` MilestoneID int64 `json:"milestone_id"`
@ -81,8 +82,9 @@ type SearchOptions struct {
RepoIDs []int64 // repository IDs which the issues belong to RepoIDs []int64 // repository IDs which the issues belong to
AllPublic bool // if include all public repositories AllPublic bool // if include all public repositories
IsPull optional.Option[bool] // if the issues is a pull request IsPull optional.Option[bool] // if the issues is a pull request
IsClosed optional.Option[bool] // if the issues is closed IsClosed optional.Option[bool] // if the issues is closed
IsArchived optional.Option[bool] // if the repo is archived
IncludedLabelIDs []int64 // labels the issues have IncludedLabelIDs []int64 // labels the issues have
ExcludedLabelIDs []int64 // labels the issues don't have ExcludedLabelIDs []int64 // labels the issues don't have

View File

@ -18,7 +18,7 @@ import (
) )
const ( const (
issueIndexerLatestVersion = 3 issueIndexerLatestVersion = 4
// TODO: make this configurable if necessary // TODO: make this configurable if necessary
maxTotalHits = 10000 maxTotalHits = 10000
@ -61,6 +61,7 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer {
"is_public", "is_public",
"is_pull", "is_pull",
"is_closed", "is_closed",
"is_archived",
"label_ids", "label_ids",
"no_label", "no_label",
"milestone_id", "milestone_id",
@ -145,6 +146,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
if options.IsClosed.Has() { if options.IsClosed.Has() {
query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.Value())) query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.Value()))
} }
if options.IsArchived.Has() {
query.And(inner_meilisearch.NewFilterEq("is_archived", options.IsArchived.Value()))
}
if options.NoLabelOnly { if options.NoLabelOnly {
query.And(inner_meilisearch.NewFilterEq("no_label", true)) query.And(inner_meilisearch.NewFilterEq("no_label", true))

View File

@ -101,6 +101,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
Comments: comments, Comments: comments,
IsPull: issue.IsPull, IsPull: issue.IsPull,
IsClosed: issue.IsClosed, IsClosed: issue.IsClosed,
IsArchived: issue.Repo.IsArchived,
LabelIDs: labels, LabelIDs: labels,
NoLabel: len(labels) == 0, NoLabel: len(labels) == 0,
MilestoneID: issue.MilestoneID, MilestoneID: issue.MilestoneID,

View File

@ -358,6 +358,7 @@ func CommonRoutes() *web.Router {
r.Get("/PACKAGES", cran.EnumerateSourcePackages) r.Get("/PACKAGES", cran.EnumerateSourcePackages)
r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages) r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages)
r.Get("/{filename}", cran.DownloadSourcePackageFile) r.Get("/{filename}", cran.DownloadSourcePackageFile)
r.Get("/Archive/{packagename}/{filename}", cran.DownloadSourcePackageFile)
}) })
r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadSourcePackageFile) r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadSourcePackageFile)
}) })

View File

@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/indexer/code"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/indexer/stats" "code.gitea.io/gitea/modules/indexer/stats"
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -905,6 +906,9 @@ func SettingsPost(ctx *context.Context) {
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
} }
// update issue indexer
issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
@ -929,6 +933,9 @@ func SettingsPost(ctx *context.Context) {
} }
} }
// update issue indexer
issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)

View File

@ -74,9 +74,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
schema, _, _ := strings.Cut(app.OpenURL, ":") schema, _, _ := strings.Cut(app.OpenURL, ":")
var iconHTML template.HTML var iconHTML template.HTML
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" { if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2") iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16)
} else { } else {
iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future iconHTML = svg.RenderHTML("gitea-git", 16) // TODO: it could support user's customized icon in the future
} }
tmplApps = append(tmplApps, map[string]any{ tmplApps = append(tmplApps, map[string]any{
"DisplayName": app.DisplayName, "DisplayName": app.DisplayName,

View File

@ -1,15 +1,13 @@
<!-- there is always at least one button (by context/repo.go) --> <!-- there is always at least one button (guaranteed by context/repo.go) -->
{{if $.CloneButtonShowHTTPS}} <div class="ui action small input clone-buttons-combo">
<button class="ui small button" id="repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}"> {{if $.CloneButtonShowHTTPS}}
HTTPS <button class="ui small button repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
{{end}}
{{if $.CloneButtonShowSSH}}
<button class="ui small button repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
{{end}}
<input size="10" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
<button class="ui small icon button" data-clipboard-target=".repo-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
{{svg "octicon-copy" 14}}
</button> </button>
{{end}} </div>
{{if $.CloneButtonShowSSH}}
<button class="ui small button" id="repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">
SSH
</button>
{{end}}
<input id="repo-clone-url" size="10" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
<button class="ui small icon button" id="clipboard-btn" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{ctx.Locale.Tr "copy_url"}}">
{{svg "octicon-copy" 14}}
</button>

View File

@ -0,0 +1,44 @@
<button class="ui green button js-btn-clone-panel">
<span>{{svg "octicon-code" 16}} Code</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</button>
<div class="clone-panel-popup tippy-target">
<div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div>
<div class="clone-panel-tab">
<!-- there is always at least one button (guaranteed by context/repo.go) -->
{{if $.CloneButtonShowHTTPS}}
<button class="item repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
{{end}}
{{if $.CloneButtonShowSSH}}
<button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
{{end}}
</div>
<div class="divider"></div>
<div class="clone-panel-field">
<div class="ui input tiny action">
<input size="30" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
<div class="ui small compact icon button" data-clipboard-target=".js-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
{{svg "octicon-copy" 14}}
</div>
</div>
</div>
{{if not .PageIsWiki}}
<div class="flex-items-block clone-panel-list">
{{range .OpenWithEditorApps}}
<a class="item muted js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
{{end}}
</div>
{{if and (not $.DisableDownloadSourceArchives) $.RefName}}
<div class="divider"></div>
<div class="flex-items-block clone-panel-list">
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_zip"}}</a>
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_tar"}}</a>
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package"}} {{ctx.Locale.Tr "repo.download_bundle"}}</a>
</div>
{{end}}
{{end}}
</div>

View File

@ -1,50 +0,0 @@
<script>
// synchronously set clone button states and urls here to avoid flickering
// on page load. initRepoCloneLink calls this when proto changes.
// this applies the protocol-dependant clone url to all elements with the
// `js-clone-url` and `js-clone-url-vsc` classes.
// TODO: This localStorage setting should be moved to backend user config
// so it's available during rendering, then this inline script can be removed.
(window.updateCloneStates = function() {
const httpsBtn = document.getElementById('repo-clone-https');
const sshBtn = document.getElementById('repo-clone-ssh');
const value = localStorage.getItem('repo-clone-protocol') || 'https';
const isSSH = value === 'ssh' && sshBtn || value !== 'ssh' && !httpsBtn;
if (httpsBtn) {
httpsBtn.textContent = window.origin.split(':')[0].toUpperCase();
httpsBtn.classList.toggle('primary', !isSSH);
httpsBtn.classList.toggle('basic', isSSH);
}
if (sshBtn) {
sshBtn.classList.toggle('primary', isSSH);
sshBtn.classList.toggle('basic', !isSSH);
}
const btn = isSSH ? sshBtn : httpsBtn;
if (!btn) return;
// NOTE: Keep this function in sync with the one in the js folder
function toOriginUrl(urlStr) {
try {
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const {origin, protocol, hostname, port} = window.location;
const url = new URL(urlStr, origin);
url.protocol = protocol;
url.hostname = hostname;
url.port = port || (protocol === 'https:' ? '443' : '80');
return url.toString();
}
} catch {}
return urlStr;
}
const link = toOriginUrl(btn.getAttribute('data-link'));
for (const el of document.getElementsByClassName('js-clone-url')) {
el[el.nodeName === 'INPUT' ? 'value' : 'textContent'] = link;
}
for (const el of document.getElementsByClassName('js-clone-url-editor')) {
el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
}
})();
</script>

View File

@ -37,9 +37,7 @@
</a> </a>
{{end}} {{end}}
{{end}} {{end}}
<div class="clone-panel ui action small input tw-flex-1"> {{template "repo/clone_buttons" .}}
{{template "repo/clone_buttons" .}}
</div>
</div> </div>
</div> </div>
@ -73,7 +71,6 @@ git push -u origin {{.Repository.DefaultBranch}}</code></pre>
{{ctx.Locale.Tr "repo.empty_message"}} {{ctx.Locale.Tr "repo.empty_message"}}
</div> </div>
{{end}} {{end}}
{{template "repo/clone_script" .}}
</div> </div>
</div> </div>
</div> </div>

View File

@ -102,27 +102,10 @@
{{end}} {{end}}
</div> </div>
{{/* by default, the row-right flex grows, but on non-root tree path, it should not because the row-left might contain a long path */}} <div class="repo-button-row-right">
<div class="repo-button-row-right {{if not $isTreePathRoot}}tw-flex-grow-0{{end}}">
<!-- Only show clone panel in repository home page --> <!-- Only show clone panel in repository home page -->
{{if $isTreePathRoot}} {{if $isTreePathRoot}}
<div class="clone-panel ui action tiny input"> {{template "repo/clone_panel" .}}
{{template "repo/clone_buttons" .}}
<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
{{if not $.DisableDownloadSourceArchives}}
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
{{end}}
{{range .OpenWithEditorApps}}
<a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
{{end}}
</div>
</button>
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
</div>
{{end}} {{end}}
{{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}} {{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}"> <a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
@ -140,6 +123,9 @@
{{template "repo/code/upstream_diverging_info" .}} {{template "repo/code/upstream_diverging_info" .}}
{{end}} {{end}}
{{template "repo/view_list" .}} {{template "repo/view_list" .}}
{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}}
{{template "repo/view_file" .}}
{{end}}
{{end}} {{end}}
</div> </div>

View File

@ -1,5 +1,5 @@
{{if not .LatestCommit}} {{if not .LatestCommit}}
<div class="ui active tiny slow centered inline"></div>
{{else}} {{else}}
{{if .LatestCommitUser}} {{if .LatestCommitUser}}
{{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}} {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}

View File

@ -1,73 +1,57 @@
<table id="repo-files-table" class="ui single line fixed table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}> {{/* use grid layout, still use the old ID because there are many other CSS styles depending on this ID */}}
<thead> <div id="repo-files-table" {{if .HasFilesWithoutLatestCommit}}hx-indicator="#repo-files-table .repo-file-cell.message" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
<tr class="commit-list"> <div class="repo-file-line">
<th class="tw-overflow-hidden" colspan="2"> <div class="latest-commit">{{template "repo/latest_commit" .}}</div>
<div class="tw-flex"> <div>{{if and .LatestCommit .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}</div>
<div class="latest-commit"> </div>
{{template "repo/latest_commit" .}} {{if .HasParentPath}}
</div> <div class="repo-file-line">
</div> {{svg "octicon-reply"}} <a class="muted" href="{{.BranchLink}}{{if .ParentPath}}{{PathEscapeSegments .ParentPath}}{{end}}">..</a>
</th> </div>
<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}{{end}}</th> {{end}}
</tr> {{range $item := .Files}}
</thead> <div class="repo-file-item">
<tbody>
{{if .HasParentPath}}
<tr class="has-parent">
<td colspan="3">{{svg "octicon-reply"}}<a class="muted" href="{{.BranchLink}}{{if .ParentPath}}{{PathEscapeSegments .ParentPath}}{{end}}">..</a></td>
</tr>
{{end}}
{{range $item := .Files}}
{{$entry := $item.Entry}} {{$entry := $item.Entry}}
{{$commit := $item.Commit}} {{$commit := $item.Commit}}
{{$subModuleFile := $item.SubModuleFile}} {{$subModuleFile := $item.SubModuleFile}}
<tr data-entryname="{{$entry.Name}}" data-ready="{{if $commit}}true{{else}}false{{end}}" class="{{if not $commit}}not{{end}}ready entry"> <div class="repo-file-cell name {{if not $commit}}notready{{end}}">
<td class="name four wide"> {{if $entry.IsSubModule}}
<span class="truncate"> {{svg "octicon-file-submodule"}}
{{if $entry.IsSubModule}} {{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}}
{{svg "octicon-file-submodule"}} {{if $refURL}}
{{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} <a class="muted" href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a href="{{$refURL}}/commit/{{PathEscape $subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a>
{{if $refURL}} {{else}}
<a class="muted" href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a href="{{$refURL}}/commit/{{PathEscape $subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a> {{$entry.Name}}<span class="at">@</span>{{ShortSha $subModuleFile.RefID}}
{{end}}
{{else}}
{{if $entry.IsDir}}
{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
{{svg "octicon-file-directory-fill"}}
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}">
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
{{if eq $subJumpablePathFieldLast 0}}
{{$subJumpablePathName}}
{{else}} {{else}}
{{$entry.Name}}<span class="at">@</span>{{ShortSha $subModuleFile.RefID}} {{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}}
<span class="text light-2">{{StringUtils.Join $subJumpablePathPrefixes "/"}}</span>/{{index $subJumpablePathFields $subJumpablePathFieldLast}}
{{end}} {{end}}
{{else}} </a>
{{if $entry.IsDir}} {{else}}
{{$subJumpablePathName := $entry.GetSubJumpablePathName}} {{svg (printf "octicon-%s" (EntryIcon $entry))}}
{{svg "octicon-file-directory-fill"}} <a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}"> {{end}}
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}} {{end}}
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}} </div>
{{if eq $subJumpablePathFieldLast 0}} <div class="repo-file-cell message loading-icon-2px">
{{$subJumpablePathName}} {{if $commit}}
{{else}} {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
{{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}} {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}}
<span class="text light-2">{{StringUtils.Join $subJumpablePathPrefixes "/"}}</span>/{{index $subJumpablePathFields $subJumpablePathFieldLast}} {{else}}
{{end}} {{/* will be loaded again by LastCommitLoaderURL */}}
</a> {{end}}
{{else}} </div>
{{svg (printf "octicon-%s" (EntryIcon $entry))}} <div class="repo-file-cell age">{{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}</div>
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a> </div>
{{end}} {{end}}
{{end}} </div>
</span>
</td>
<td class="message nine wide">
<span class="truncate">
{{if $commit}}
{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}}
{{else}}
<div class="ui active tiny slow centered inline"></div>
{{end}}
</span>
</td>
<td class="text right age three wide">{{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}}
{{template "repo/view_file" .}}
{{end}}

View File

@ -15,10 +15,7 @@
</div> </div>
</div> </div>
<div class="ui eight wide column text right"> <div class="ui eight wide column text right">
<div class="clone-panel ui action small input"> {{template "repo/clone_panel" .}}
{{template "repo/clone_buttons" .}}
{{template "repo/clone_script" .}}
</div>
</div> </div>
</div> </div>
<h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2> <h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2>

View File

@ -4,7 +4,7 @@
{{$title := .title}} {{$title := .title}}
<div class="ui container"> <div class="ui container">
<div class="repo-button-row"> <div class="repo-button-row">
<div class="tw-flex tw-items-center"> <div class="flex-text-block tw-flex-1">
<div class="ui floating filter dropdown" data-no-results="{{ctx.Locale.Tr "no_results_found"}}"> <div class="ui floating filter dropdown" data-no-results="{{ctx.Locale.Tr "no_results_found"}}">
<div class="ui basic small button"> <div class="ui basic small button">
<span class="text"> <span class="text">
@ -28,10 +28,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="clone-panel ui action small input"> {{template "repo/clone_panel" .}}
{{template "repo/clone_buttons" .}}
{{template "repo/clone_script" .}}
</div>
</div> </div>
<div class="ui dividing header"> <div class="ui dividing header">
<div class="flex-text-block tw-flex-wrap tw-justify-end"> <div class="flex-text-block tw-flex-wrap tw-justify-end">
@ -45,7 +42,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="flex-text-block tw-flex-wrap tw-justify-end"> <div class="repo-button-row">
{{if .EscapeStatus.Escaped}} {{if .EscapeStatus.Escaped}}
<a class="ui small button unescape-button tw-m-0 tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a> <a class="ui small button unescape-button tw-m-0 tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui small button escape-button tw-m-0">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a> <a class="ui small button escape-button tw-m-0">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>

View File

@ -115,6 +115,14 @@ func TestPackageCran(t *testing.T) {
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)
}) })
t.Run("DownloadArchived", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("%s/src/contrib/Archive/%s/%s_%s.tar.gz", url, packageName, packageName, packageVersion)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
})
t.Run("Enumerate", func(t *testing.T) { t.Run("Enumerate", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()

View File

@ -49,7 +49,7 @@ func testViewRepo(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR") files := htmlDoc.doc.Find("#repo-files-table .repo-file-item")
type file struct { type file struct {
fileName string fileName string
@ -61,7 +61,7 @@ func testViewRepo(t *testing.T) {
var items []file var items []file
files.Each(func(i int, s *goquery.Selection) { files.Each(func(i int, s *goquery.Selection) {
tds := s.Find("td") tds := s.Find(".repo-file-cell")
var f file var f file
tds.Each(func(i int, s *goquery.Selection) { tds.Each(func(i int, s *goquery.Selection) {
if i == 0 { if i == 0 {
@ -127,10 +127,10 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link") link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed") assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link) assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
_, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link") _, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.False(t, exists) assert.False(t, exists)
} }
@ -143,10 +143,10 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link") link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed") assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link) assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
link, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link") link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.True(t, exists, "The template has changed") assert.True(t, exists, "The template has changed")
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port) sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
assert.Equal(t, sshURL, link) assert.Equal(t, sshURL, link)
@ -161,7 +161,7 @@ func TestViewRepoWithSymlinks(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name > SPAN.truncate") files := htmlDoc.doc.Find("#repo-files-table .repo-file-cell.name")
items := files.Map(func(i int, s *goquery.Selection) string { items := files.Map(func(i int, s *goquery.Selection) string {
cls, _ := s.Find("SVG").Attr("class") cls, _ := s.Find("SVG").Attr("class")
file := strings.Trim(s.Find("A").Text(), " \t\n") file := strings.Trim(s.Find("A").Text(), " \t\n")

View File

@ -66,7 +66,10 @@
@import "./repo/wiki.css"; @import "./repo/wiki.css";
@import "./repo/header.css"; @import "./repo/header.css";
@import "./repo/home.css"; @import "./repo/home.css";
@import "./repo/home-file-list.css";
@import "./repo/reactions.css"; @import "./repo/reactions.css";
@import "./repo/clone.css";
@import "./repo/commit-sign.css";
@import "./editor/fileeditor.css"; @import "./editor/fileeditor.css";
@import "./editor/combomarkdowneditor.css"; @import "./editor/combomarkdowneditor.css";

View File

@ -101,42 +101,6 @@
margin-bottom: 12px; margin-bottom: 12px;
} }
.repository .clone-panel {
display: flex;
flex: 1;
}
.repository.wiki .clone-panel {
flex: 0;
}
.repository.wiki .clone-panel input {
width: 20ch;
}
.repository .clone-panel #repo-clone-url {
border-radius: 0;
flex: 1;
}
.repository .ui.action.input.clone-panel > button + button,
.repository .ui.action.input.clone-panel > button + input {
margin-left: -1px; /* make the borders overlap to avoid double borders */
}
.repository .clone-panel > button:first-of-type {
border-radius: var(--border-radius) 0 0 var(--border-radius) !important;
}
.repository .clone-panel > button:last-of-type {
border-radius: 0 var(--border-radius) var(--border-radius) 0 !important;
}
.repository .clone-panel .dropdown .menu {
right: 0 !important;
left: auto !important;
}
.repository .repo-description { .repository .repo-description {
font-size: 16px; font-size: 16px;
margin-bottom: 5px; margin-bottom: 5px;
@ -177,138 +141,6 @@ td .commit-summary {
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
/* this is what limits the commit table width to a value that works on all viewport sizes */
#repo-files-table th:first-of-type {
max-width: calc(calc(min(100vw, 1280px)) - 145px - calc(2 * var(--page-margin-x)));
}
.repository.file.list #repo-files-table thead th {
font-weight: var(--font-weight-normal);
}
.repository.file.list #repo-files-table tbody .svg {
margin-left: 3px;
margin-right: 5px;
}
.repository.file.list #repo-files-table tbody .svg.octicon-reply {
margin-right: 10px;
}
.repository.file.list #repo-files-table tbody .svg.octicon-file-directory-fill,
.repository.file.list #repo-files-table tbody .svg.octicon-file-submodule {
color: var(--color-primary);
}
.repository.file.list #repo-files-table tbody .svg.octicon-file,
.repository.file.list #repo-files-table tbody .svg.octicon-file-symlink-file,
.repository.file.list #repo-files-table tbody .svg.octicon-file-directory-symlink {
color: var(--color-secondary-dark-7);
}
.repository.file.list #repo-files-table td {
padding-top: 0;
padding-bottom: 0;
overflow: initial;
}
.repository.file.list #repo-files-table td.name {
width: 33%;
max-width: calc(100vw - 140px);
}
@media (min-width: 1201px) {
.repository.file.list #repo-files-table td.name {
max-width: 150px;
}
}
@media (min-width: 992px) and (max-width: 1200px) {
.repository.file.list #repo-files-table td.name {
max-width: 200px;
}
}
@media (min-width: 768px) and (max-width: 991.98px) {
.repository.file.list #repo-files-table td.name {
max-width: 300px;
}
}
.repository.file.list #repo-files-table td.message {
color: var(--color-text-light-1);
width: 66%;
}
@media (min-width: 1201px) {
.repository.file.list #repo-files-table td.message {
max-width: 400px;
}
}
@media (min-width: 992px) and (max-width: 1200px) {
.repository.file.list #repo-files-table td.message {
max-width: 350px;
}
}
@media (min-width: 768px) and (max-width: 991.98px) {
.repository.file.list #repo-files-table td.message {
max-width: 250px;
}
}
.repository.file.list #repo-files-table td.age {
color: var(--color-text-light-1);
}
.repository.file.list #repo-files-table td .truncate {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
padding-top: 8px;
padding-bottom: 8px;
}
.repository.file.list #repo-files-table td a {
padding-top: 8px;
padding-bottom: 8px;
}
.repository.file.list #repo-files-table td .at {
margin-left: 3px;
margin-right: 3px;
}
.repository.file.list #repo-files-table td > * {
vertical-align: middle;
}
.repository.file.list #repo-files-table td.message .isSigned {
cursor: default;
}
.repository.file.list #repo-files-table tr:last-of-type td:first-child {
border-bottom-left-radius: var(--border-radius);
}
.repository.file.list #repo-files-table tr:last-of-type td:last-child {
border-bottom-right-radius: var(--border-radius);
}
.repository.file.list #repo-files-table tr:hover {
background-color: var(--color-hover);
}
.repository.file.list #repo-files-table tr.has-parent a {
display: inline-block;
padding-top: 8px;
padding-bottom: 8px;
width: calc(100% - 1.25rem);
}
.repository.file.list .non-diff-file-content .header .icon { .repository.file.list .non-diff-file-content .header .icon {
font-size: 1em; font-size: 1em;
} }
@ -787,47 +619,6 @@ td .commit-summary {
height: 30px !important; height: 30px !important;
} }
.singular-commit .shabox .sha.label {
margin: 0;
border: 1px solid var(--color-light-border);
}
.singular-commit .shabox .sha.label.isSigned.isWarning {
border: 1px solid var(--color-red-badge);
background: var(--color-red-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isWarning:hover {
background: var(--color-red-badge-hover-bg) !important;
}
.singular-commit .shabox .sha.label.isSigned.isVerified {
border: 1px solid var(--color-green-badge);
background: var(--color-green-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isVerified:hover {
background: var(--color-green-badge-hover-bg) !important;
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted {
border: 1px solid var(--color-yellow-badge);
background: var(--color-yellow-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted:hover {
background: var(--color-yellow-badge-hover-bg) !important;
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched {
border: 1px solid var(--color-orange-badge);
background: var(--color-orange-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched:hover {
background: var(--color-orange-badge-hover-bg) !important;
}
.repository.view.issue .comment-list .timeline-item.event > .commit-status-link { .repository.view.issue .comment-list .timeline-item.event > .commit-status-link {
float: right; float: right;
margin-right: 8px; margin-right: 8px;
@ -1162,151 +953,6 @@ td .commit-summary {
background-color: var(--color-light) !important; background-color: var(--color-light) !important;
} }
.repository #commits-table td.sha .sha.label,
.repository #repo-files-table .sha.label,
.repository #repo-file-commit-box .sha.label,
.repository #rev-list .sha.label,
.repository .timeline-item.commits-list .singular-commit .sha.label {
border: 1px solid var(--color-light-border);
}
.repository #commits-table td.sha .sha.label .detail.icon,
.repository #repo-files-table .sha.label .detail.icon,
.repository #repo-file-commit-box .sha.label .detail.icon,
.repository #rev-list .sha.label .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon {
background: var(--color-light);
margin: -6px -10px -4px 0;
padding: 5px 4px 5px 6px;
border-left: 1px solid var(--color-light-border);
border-top: 0;
border-right: 0;
border-bottom: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.repository #commits-table td.sha .sha.label .detail.icon .svg,
.repository #repo-files-table .sha.label .detail.icon .svg,
.repository #repo-file-commit-box .sha.label .detail.icon .svg,
.repository #rev-list .sha.label .detail.icon .svg,
.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon .svg {
margin: 0 0.25em 0 0;
}
.repository #commits-table td.sha .sha.label .detail.icon > div,
.repository #repo-files-table .sha.label .detail.icon > div,
.repository #repo-file-commit-box .sha.label .detail.icon > div,
.repository #rev-list .sha.label .detail.icon > div,
.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon > div {
display: flex;
align-items: center;
}
.repository #commits-table td.sha .sha.label.isSigned.isWarning,
.repository #repo-files-table .sha.label.isSigned.isWarning,
.repository #repo-file-commit-box .sha.label.isSigned.isWarning,
.repository #rev-list .sha.label.isSigned.isWarning,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning {
border: 1px solid var(--color-red-badge);
background: var(--color-red-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isWarning .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isWarning .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isWarning .detail.icon,
.repository #rev-list .sha.label.isSigned.isWarning .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning .detail.icon {
border-left: 1px solid var(--color-red-badge);
color: var(--color-red-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isWarning:hover,
.repository #repo-files-table .sha.label.isSigned.isWarning:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isWarning:hover,
.repository #rev-list .sha.label.isSigned.isWarning:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning:hover {
background: var(--color-red-badge-hover-bg) !important;
}
.repository #commits-table td.sha .sha.label.isSigned.isVerified,
.repository #repo-files-table .sha.label.isSigned.isVerified,
.repository #repo-file-commit-box .sha.label.isSigned.isVerified,
.repository #rev-list .sha.label.isSigned.isVerified,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified {
border: 1px solid var(--color-green-badge);
background: var(--color-green-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isVerified .detail.icon,
.repository #rev-list .sha.label.isSigned.isVerified .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified .detail.icon {
border-left: 1px solid var(--color-green-badge);
color: var(--color-green-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerified:hover,
.repository #repo-files-table .sha.label.isSigned.isVerified:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isVerified:hover,
.repository #rev-list .sha.label.isSigned.isVerified:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified:hover {
background: var(--color-green-badge-hover-bg) !important;
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted,
.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted {
border: 1px solid var(--color-yellow-badge);
background: var(--color-yellow-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted .detail.icon {
border-left: 1px solid var(--color-yellow-badge);
color: var(--color-yellow-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted:hover {
background: var(--color-yellow-badge-hover-bg) !important;
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched,
.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched {
border: 1px solid var(--color-orange-badge);
background: var(--color-orange-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched .detail.icon {
border-left: 1px solid var(--color-orange-badge);
color: var(--color-orange-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched:hover {
background: var(--color-orange-badge-hover-bg) !important;
}
.repository .data-table { .repository .data-table {
width: 100%; width: 100%;
} }
@ -1615,14 +1261,6 @@ td .commit-summary {
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
} }
.repository.quickstart .guide #repo-clone-url {
border-radius: 0;
padding: 5px 10px;
font-size: 1.2em;
line-height: 1.4;
flex: 1
}
.empty-placeholder { .empty-placeholder {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -1678,92 +1316,6 @@ td .commit-summary {
padding-top: 0; padding-top: 0;
} }
.repository .ui.attached.isSigned.isWarning {
border-left: 1px solid var(--color-error-border);
border-right: 1px solid var(--color-error-border);
}
.repository .ui.attached.isSigned.isWarning.top,
.repository .ui.attached.isSigned.isWarning.message {
border-top: 1px solid var(--color-error-border);
}
.repository .ui.attached.isSigned.isWarning.message {
box-shadow: none;
background-color: var(--color-error-bg);
color: var(--color-error-text);
}
.repository .ui.attached.isSigned.isWarning.message .ui.text {
color: var(--color-error-text);
}
.repository .ui.attached.isSigned.isWarning:last-child,
.repository .ui.attached.isSigned.isWarning.bottom {
border-bottom: 1px solid var(--color-error-border);
}
.repository .ui.attached.isSigned.isVerified {
border-left: 1px solid var(--color-success-border);
border-right: 1px solid var(--color-success-border);
}
.repository .ui.attached.isSigned.isVerified.top,
.repository .ui.attached.isSigned.isVerified.message {
border-top: 1px solid var(--color-success-border);
}
.repository .ui.attached.isSigned.isVerified.message {
box-shadow: none;
background-color: var(--color-success-bg);
color: var(--color-success-text);
}
.repository .ui.attached.isSigned.isVerified.message .pull-right {
color: var(--color-text);
}
.repository .ui.attached.isSigned.isVerified.message .ui.text {
color: var(--color-success-text);
}
.repository .ui.attached.isSigned.isVerified:last-child,
.repository .ui.attached.isSigned.isVerified.bottom {
border-bottom: 1px solid var(--color-success-border);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted,
.repository .ui.attached.isSigned.isVerifiedUnmatched {
border-left: 1px solid var(--color-warning-border);
border-right: 1px solid var(--color-warning-border);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted.top,
.repository .ui.attached.isSigned.isVerifiedUnmatched.top,
.repository .ui.attached.isSigned.isVerifiedUntrusted.message,
.repository .ui.attached.isSigned.isVerifiedUnmatched.message {
border-top: 1px solid var(--color-warning-border);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted.message,
.repository .ui.attached.isSigned.isVerifiedUnmatched.message {
box-shadow: none;
background-color: var(--color-warning-bg);
color: var(--color-warning-text);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted.message .ui.text,
.repository .ui.attached.isSigned.isVerifiedUnmatched.message .ui.text {
color: var(--color-warning-text);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted:last-child,
.repository .ui.attached.isSigned.isVerifiedUnmatched:last-child,
.repository .ui.attached.isSigned.isVerifiedUntrusted.bottom,
.repository .ui.attached.isSigned.isVerifiedUnmatched.bottom {
border-bottom: 1px solid var(--color-warning-border);
}
.repository .ui.fluid.action.input .ui.search.action.input { .repository .ui.fluid.action.input .ui.search.action.input {
flex: auto; flex: auto;
} }
@ -1782,7 +1334,7 @@ td .commit-summary {
.repository .repository-summary .sub-menu .item { .repository .repository-summary .sub-menu .item {
flex: 1; flex: 1;
height: 30px; height: 33px; /* match search bar height, need to be improved in the future to use some consistent methods */
line-height: var(--line-height-default); line-height: var(--line-height-default);
display: flex; display: flex;
align-items: center; align-items: center;
@ -2110,26 +1662,18 @@ td .commit-summary {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
justify-content: space-between; flex-wrap: wrap;
} }
.repo-button-row-left, .repo-button-row-left,
.repo-button-row-right { .repo-button-row-right {
display: flex; display: flex;
flex: 1;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
} }
.repo-button-row-right { .repo-button-row-left {
justify-content: flex-end; flex: 1;
}
@media (max-width: 1200px) {
.repository:not(.wiki) .repo-button-row {
flex-direction: column;
align-items: stretch;
}
} }
.repo-button-row .button { .repo-button-row .button {
@ -2143,16 +1687,6 @@ td .commit-summary {
padding-right: 22px !important; /* normal buttons have !important paddings, so we need to override it for dropdown (Add File) icons */ padding-right: 22px !important; /* normal buttons have !important paddings, so we need to override it for dropdown (Add File) icons */
} }
.repo-button-row input {
height: 30px;
}
@media (max-width: 600px) {
.repo-button-row-left {
flex-wrap: wrap;
}
}
tbody.commit-list { tbody.commit-list {
vertical-align: baseline; vertical-align: baseline;
} }
@ -2178,11 +1712,6 @@ tbody.commit-list {
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
/* but in the repo-files-table we cannot */
#repo-files-table .commit-list .message-wrapper {
display: inline-block;
}
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
tr.commit-list { tr.commit-list {
width: 100%; width: 100%;
@ -2578,25 +2107,6 @@ tbody.commit-list {
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
.repository.file.list #repo-files-table .entry,
.repository.file.list #repo-files-table .commit-list {
align-items: center;
display: flex !important;
padding-top: 4px;
padding-bottom: 4px;
}
.repository.file.list #repo-files-table .entry td.age,
.repository.file.list #repo-files-table .commit-list td.age,
.repository.file.list #repo-files-table .entry th.age,
.repository.file.list #repo-files-table .commit-list th.age {
margin-left: auto;
}
.repository.file.list #repo-files-table .entry td.message,
.repository.file.list #repo-files-table .commit-list td.message,
.repository.file.list #repo-files-table .entry span.commit-summary,
.repository.file.list #repo-files-table .commit-list tr span.commit-summary {
display: none !important;
}
.repository.view.issue .comment-list .timeline, .repository.view.issue .comment-list .timeline,
.repository.view.issue .comment-list .timeline-item { .repository.view.issue .comment-list .timeline-item {
margin-left: 0; margin-left: 0;

View File

@ -0,0 +1,32 @@
/* only used by "repo/empty.tmpl" */
.clone-buttons-combo {
flex: 1;
}
.clone-buttons-combo input {
border-left: none !important;
border-radius: 0 !important;
}
/* used by the clone-panel popup */
.clone-panel-field,
.clone-panel-list {
margin: 10px;
}
.clone-panel-tab .item {
padding: 5px 10px;
background: none;
}
.clone-panel-tab .item.active {
border-bottom: 3px solid var(--color-secondary);
}
.clone-panel-tab + .divider {
margin: -1px 0 0;
}
.clone-panel-list .item {
margin: 5px 0;
}

View File

@ -0,0 +1,272 @@
.repository .ui.attached.isSigned.isWarning {
border-left: 1px solid var(--color-error-border);
border-right: 1px solid var(--color-error-border);
}
.repository .ui.attached.isSigned.isWarning.top,
.repository .ui.attached.isSigned.isWarning.message {
border-top: 1px solid var(--color-error-border);
}
.repository .ui.attached.isSigned.isWarning.message {
box-shadow: none;
background-color: var(--color-error-bg);
color: var(--color-error-text);
}
.repository .ui.attached.isSigned.isWarning.message .ui.text {
color: var(--color-error-text);
}
.repository .ui.attached.isSigned.isWarning:last-child,
.repository .ui.attached.isSigned.isWarning.bottom {
border-bottom: 1px solid var(--color-error-border);
}
.repository .ui.attached.isSigned.isVerified {
border-left: 1px solid var(--color-success-border);
border-right: 1px solid var(--color-success-border);
}
.repository .ui.attached.isSigned.isVerified.top,
.repository .ui.attached.isSigned.isVerified.message {
border-top: 1px solid var(--color-success-border);
}
.repository .ui.attached.isSigned.isVerified.message {
box-shadow: none;
background-color: var(--color-success-bg);
color: var(--color-success-text);
}
.repository .ui.attached.isSigned.isVerified.message .pull-right {
color: var(--color-text);
}
.repository .ui.attached.isSigned.isVerified.message .ui.text {
color: var(--color-success-text);
}
.repository .ui.attached.isSigned.isVerified:last-child,
.repository .ui.attached.isSigned.isVerified.bottom {
border-bottom: 1px solid var(--color-success-border);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted,
.repository .ui.attached.isSigned.isVerifiedUnmatched {
border-left: 1px solid var(--color-warning-border);
border-right: 1px solid var(--color-warning-border);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted.top,
.repository .ui.attached.isSigned.isVerifiedUnmatched.top,
.repository .ui.attached.isSigned.isVerifiedUntrusted.message,
.repository .ui.attached.isSigned.isVerifiedUnmatched.message {
border-top: 1px solid var(--color-warning-border);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted.message,
.repository .ui.attached.isSigned.isVerifiedUnmatched.message {
box-shadow: none;
background-color: var(--color-warning-bg);
color: var(--color-warning-text);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted.message .ui.text,
.repository .ui.attached.isSigned.isVerifiedUnmatched.message .ui.text {
color: var(--color-warning-text);
}
.repository .ui.attached.isSigned.isVerifiedUntrusted:last-child,
.repository .ui.attached.isSigned.isVerifiedUnmatched:last-child,
.repository .ui.attached.isSigned.isVerifiedUntrusted.bottom,
.repository .ui.attached.isSigned.isVerifiedUnmatched.bottom {
border-bottom: 1px solid var(--color-warning-border);
}
.repository #commits-table td.sha .sha.label,
.repository #repo-files-table .sha.label,
.repository #repo-file-commit-box .sha.label,
.repository #rev-list .sha.label,
.repository .timeline-item.commits-list .singular-commit .sha.label {
border: 1px solid var(--color-light-border);
}
.repository #commits-table td.sha .sha.label .detail.icon,
.repository #repo-files-table .sha.label .detail.icon,
.repository #repo-file-commit-box .sha.label .detail.icon,
.repository #rev-list .sha.label .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon {
background: var(--color-light);
margin: -6px -10px -4px 0;
padding: 5px 4px 5px 6px;
border-left: 1px solid var(--color-light-border);
border-top: 0;
border-right: 0;
border-bottom: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.repository #commits-table td.sha .sha.label .detail.icon .svg,
.repository #repo-files-table .sha.label .detail.icon .svg,
.repository #repo-file-commit-box .sha.label .detail.icon .svg,
.repository #rev-list .sha.label .detail.icon .svg,
.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon .svg {
margin: 0 0.25em 0 0;
}
.repository #commits-table td.sha .sha.label .detail.icon > div,
.repository #repo-files-table .sha.label .detail.icon > div,
.repository #repo-file-commit-box .sha.label .detail.icon > div,
.repository #rev-list .sha.label .detail.icon > div,
.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon > div {
display: flex;
align-items: center;
}
.repository #commits-table td.sha .sha.label.isSigned.isWarning,
.repository #repo-files-table .sha.label.isSigned.isWarning,
.repository #repo-file-commit-box .sha.label.isSigned.isWarning,
.repository #rev-list .sha.label.isSigned.isWarning,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning {
border: 1px solid var(--color-red-badge);
background: var(--color-red-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isWarning .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isWarning .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isWarning .detail.icon,
.repository #rev-list .sha.label.isSigned.isWarning .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning .detail.icon {
border-left: 1px solid var(--color-red-badge);
color: var(--color-red-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isWarning:hover,
.repository #repo-files-table .sha.label.isSigned.isWarning:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isWarning:hover,
.repository #rev-list .sha.label.isSigned.isWarning:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning:hover {
background: var(--color-red-badge-hover-bg) !important;
}
.repository #commits-table td.sha .sha.label.isSigned.isVerified,
.repository #repo-files-table .sha.label.isSigned.isVerified,
.repository #repo-file-commit-box .sha.label.isSigned.isVerified,
.repository #rev-list .sha.label.isSigned.isVerified,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified {
border: 1px solid var(--color-green-badge);
background: var(--color-green-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isVerified .detail.icon,
.repository #rev-list .sha.label.isSigned.isVerified .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified .detail.icon {
border-left: 1px solid var(--color-green-badge);
color: var(--color-green-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerified:hover,
.repository #repo-files-table .sha.label.isSigned.isVerified:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isVerified:hover,
.repository #rev-list .sha.label.isSigned.isVerified:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified:hover {
background: var(--color-green-badge-hover-bg) !important;
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted,
.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted {
border: 1px solid var(--color-yellow-badge);
background: var(--color-yellow-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted .detail.icon {
border-left: 1px solid var(--color-yellow-badge);
color: var(--color-yellow-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted:hover {
background: var(--color-yellow-badge-hover-bg) !important;
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched,
.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched {
border: 1px solid var(--color-orange-badge);
background: var(--color-orange-badge-bg);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched .detail.icon {
border-left: 1px solid var(--color-orange-badge);
color: var(--color-orange-badge);
}
.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched:hover,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched:hover {
background: var(--color-orange-badge-hover-bg) !important;
}
.singular-commit .shabox .sha.label {
margin: 0;
border: 1px solid var(--color-light-border);
}
.singular-commit .shabox .sha.label.isSigned.isWarning {
border: 1px solid var(--color-red-badge);
background: var(--color-red-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isWarning:hover {
background: var(--color-red-badge-hover-bg) !important;
}
.singular-commit .shabox .sha.label.isSigned.isVerified {
border: 1px solid var(--color-green-badge);
background: var(--color-green-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isVerified:hover {
background: var(--color-green-badge-hover-bg) !important;
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted {
border: 1px solid var(--color-yellow-badge);
background: var(--color-yellow-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted:hover {
background: var(--color-yellow-badge-hover-bg) !important;
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched {
border: 1px solid var(--color-orange-badge);
background: var(--color-orange-badge-bg);
}
.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched:hover {
background: var(--color-orange-badge-hover-bg) !important;
}

View File

@ -0,0 +1,70 @@
#repo-files-table {
width: 100%;
display: grid;
grid-template-columns: auto 1fr auto;
border: 1px solid var(--color-light-border);
border-radius: var(--border-radius);
margin: 10px 0; /* match the "clone-panel-popup" margin to avoid "visual double-border" */
}
#repo-files-table .svg.octicon-file-directory-fill,
#repo-files-table .svg.octicon-file-submodule {
color: var(--color-primary);
}
#repo-files-table .svg.octicon-file,
#repo-files-table .svg.octicon-file-symlink-file,
#repo-files-table .svg.octicon-file-directory-symlink {
color: var(--color-secondary-dark-7);
}
#repo-files-table .repo-file-item {
display: contents;
}
#repo-files-table .repo-file-item:hover > .repo-file-cell {
background: var(--color-hover);
}
#repo-files-table .repo-file-line,
#repo-files-table .repo-file-cell {
border-top: 1px solid var(--color-light-border);
padding: 6px 10px;
}
#repo-files-table .repo-file-line:first-child {
border-top: none;
}
#repo-files-table .repo-file-line {
grid-column: 1 / span 3;
display: flex;
align-items: center;
gap: 0.5em;
padding: 6px 10px;
}
#repo-files-table .repo-file-cell.name {
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#repo-files-table .repo-file-cell.message {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--color-text-light-1);
}
#repo-files-table .repo-file-cell.age {
white-space: nowrap;
color: var(--color-text-light-1);
}
@media (max-width: 767.98px) {
#repo-files-table .repo-file-cell.name {
max-width: 150px;
}
}

View File

@ -59,9 +59,6 @@
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
.repository.wiki .clone-panel #repo-clone-url {
width: 160px;
}
.repository.wiki .wiki-content-main.with-sidebar, .repository.wiki .wiki-content-main.with-sidebar,
.repository.wiki .wiki-content-sidebar { .repository.wiki .wiki-content-sidebar {
float: none; float: none;

View File

@ -11,7 +11,7 @@ function initRepoCreateBranchButton() {
for (const el of document.querySelectorAll('.show-create-branch-modal')) { for (const el of document.querySelectorAll('.show-create-branch-modal')) {
el.addEventListener('click', () => { el.addEventListener('click', () => {
const modalFormName = el.getAttribute('data-modal-form') || '#create-branch-form'; const modalFormName = el.getAttribute('data-modal-form') || '#create-branch-form';
const modalForm = document.querySelector(modalFormName); const modalForm = document.querySelector<HTMLFormElement>(modalFormName);
if (!modalForm) return; if (!modalForm) return;
modalForm.action = `${modalForm.getAttribute('data-base-action')}${el.getAttribute('data-branch-from-urlcomponent')}`; modalForm.action = `${modalForm.getAttribute('data-base-action')}${el.getAttribute('data-branch-from-urlcomponent')}`;
@ -29,7 +29,7 @@ function initRepoRenameBranchButton() {
const target = el.getAttribute('data-modal'); const target = el.getAttribute('data-modal');
const modal = document.querySelector(target); const modal = document.querySelector(target);
const oldBranchName = el.getAttribute('data-old-branch-name'); const oldBranchName = el.getAttribute('data-old-branch-name');
modal.querySelector('input[name=from]').value = oldBranchName; modal.querySelector<HTMLInputElement>('input[name=from]').value = oldBranchName;
// display the warning that the branch which is chosen is the default branch // display the warning that the branch which is chosen is the default branch
const warn = modal.querySelector('.default-branch-warning'); const warn = modal.querySelector('.default-branch-warning');

View File

@ -8,7 +8,7 @@ import {toAbsoluteUrl} from '../utils.ts';
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/; export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/; export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/;
function changeHash(hash) { function changeHash(hash: string) {
if (window.history.pushState) { if (window.history.pushState) {
window.history.pushState(null, null, hash); window.history.pushState(null, null, hash);
} else { } else {
@ -24,7 +24,7 @@ function getLineEls() {
return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`); return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`);
} }
function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
for (const el of $linesEls) { for (const el of $linesEls) {
el.closest('tr').classList.remove('active'); el.closest('tr').classList.remove('active');
} }
@ -34,7 +34,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
const copyPermalink = document.querySelector('a.copy-line-permalink'); const copyPermalink = document.querySelector('a.copy-line-permalink');
const viewGitBlame = document.querySelector('a.view_git_blame'); const viewGitBlame = document.querySelector('a.view_git_blame');
const updateIssueHref = function (anchor) { const updateIssueHref = function (anchor: string) {
if (!refInNewIssue) return; if (!refInNewIssue) return;
const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new'); const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new');
const urlParamBodyLink = refInNewIssue.getAttribute('data-url-param-body-link'); const urlParamBodyLink = refInNewIssue.getAttribute('data-url-param-body-link');
@ -42,7 +42,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`); refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
}; };
const updateViewGitBlameFragment = function (anchor) { const updateViewGitBlameFragment = function (anchor: string) {
if (!viewGitBlame) return; if (!viewGitBlame) return;
let href = viewGitBlame.getAttribute('href'); let href = viewGitBlame.getAttribute('href');
href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`; href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`;
@ -52,7 +52,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
viewGitBlame.setAttribute('href', href); viewGitBlame.setAttribute('href', href);
}; };
const updateCopyPermalinkUrl = function (anchor) { const updateCopyPermalinkUrl = function (anchor: string) {
if (!copyPermalink) return; if (!copyPermalink) return;
let link = copyPermalink.getAttribute('data-url'); let link = copyPermalink.getAttribute('data-url');
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`; link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
@ -142,13 +142,7 @@ export function initRepoCodeView() {
}); });
} }
selectRange($(linesEls), $(selectedEls), from ? $(from) : null); selectRange($(linesEls), $(selectedEls), from ? $(from) : null);
window.getSelection().removeAllRanges();
if (window.getSelection) {
window.getSelection().removeAllRanges();
} else {
document.selection.empty();
}
showLineButton(); showLineButton();
}); });

View File

@ -1,10 +1,11 @@
import $ from 'jquery'; import {queryElems} from '../utils/dom.ts';
import {hideElem, queryElems, showElem} from '../utils/dom.ts';
import {POST} from '../modules/fetch.ts'; import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts'; import {showErrorToast} from '../modules/toast.ts';
import {sleep} from '../utils.ts'; import {sleep} from '../utils.ts';
import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue'; import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';
import {createApp} from 'vue'; import {createApp} from 'vue';
import {toOriginUrl} from '../utils/url.ts';
import {createTippy} from '../modules/tippy.ts';
async function onDownloadArchive(e) { async function onDownloadArchive(e) {
e.preventDefault(); e.preventDefault();
@ -41,54 +42,69 @@ export function initRepoActivityTopAuthorsChart() {
} }
} }
export function initRepoCloneLink() { function initCloneSchemeUrlSelection(parent: Element) {
const $repoCloneSsh = $('#repo-clone-ssh'); const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
const $repoCloneHttps = $('#repo-clone-https');
const $inputLink = $('#repo-clone-url');
if ((!$repoCloneSsh.length && !$repoCloneHttps.length) || !$inputLink.length) { const tabSsh = parent.querySelector('.repo-clone-ssh');
return; const tabHttps = parent.querySelector('.repo-clone-https');
} const updateClonePanelUi = function() {
const scheme = localStorage.getItem('repo-clone-protocol') || 'https';
const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps;
if (tabHttps) {
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
tabHttps.classList.toggle('active', !isSSH);
}
if (tabSsh) {
tabSsh.classList.toggle('active', isSSH);
}
$repoCloneSsh.on('click', () => { const tab = isSSH ? tabSsh : tabHttps;
localStorage.setItem('repo-clone-protocol', 'ssh'); if (!tab) return;
window.updateCloneStates(); const link = toOriginUrl(tab.getAttribute('data-link'));
});
$repoCloneHttps.on('click', () => {
localStorage.setItem('repo-clone-protocol', 'https');
window.updateCloneStates();
});
$inputLink.on('focus', () => { for (const el of document.querySelectorAll('.js-clone-url')) {
$inputLink.trigger('select'); if (el.nodeName === 'INPUT') {
}); (el as HTMLInputElement).value = link;
} } else {
el.textContent = link;
export function initRepoCommonBranchOrTagDropdown(selector) {
$(selector).each(function () {
const $dropdown = $(this);
$dropdown.find('.reference.column').on('click', function () {
hideElem($dropdown.find('.scrolling.reference-list-menu'));
showElem($($(this).data('target')));
return false;
});
});
}
export function initRepoCommonFilterSearchDropdown(selector) {
const $dropdown = $(selector);
if (!$dropdown.length) return;
$dropdown.dropdown({
fullTextSearch: 'exact',
selectOnKeydown: false,
onChange(_text, _value, $choice) {
if ($choice[0].getAttribute('data-url')) {
window.location.href = $choice[0].getAttribute('data-url');
} }
}, }
message: {noResults: $dropdown[0].getAttribute('data-no-results')}, for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) {
el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
}
};
updateClonePanelUi();
tabSsh.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'ssh');
updateClonePanelUi();
}); });
tabHttps.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'https');
updateClonePanelUi();
});
elCloneUrlInput.addEventListener('focus', () => {
elCloneUrlInput.select();
});
}
function initClonePanelButton(btn: HTMLButtonElement) {
const elPanel = btn.nextElementSibling;
// "init" must be before the "createTippy" otherwise the "tippy-target" will be removed from the document
initCloneSchemeUrlSelection(elPanel);
createTippy(btn, {
content: elPanel,
trigger: 'click',
placement: 'bottom-end',
interactive: true,
hideOnClick: true,
});
}
export function initRepoCloneButtons() {
queryElems(document, '.js-btn-clone-panel', initClonePanelButton);
queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection);
} }
export async function updateIssuesMeta(url, action, issue_ids, id) { export async function updateIssuesMeta(url, action, issue_ids, id) {

View File

@ -1,7 +1,7 @@
import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
import {GET} from '../modules/fetch.ts'; import {GET} from '../modules/fetch.ts';
async function loadBranchesAndTags(area, loadingButton) { async function loadBranchesAndTags(area: Element, loadingButton: Element) {
loadingButton.classList.add('disabled'); loadingButton.classList.add('disabled');
try { try {
const res = await GET(loadingButton.getAttribute('data-fetch-url')); const res = await GET(loadingButton.getAttribute('data-fetch-url'));
@ -15,7 +15,7 @@ async function loadBranchesAndTags(area, loadingButton) {
} }
} }
function addTags(area, tags) { function addTags(area: Element, tags: Array<Record<string, any>>) {
const tagArea = area.querySelector('.tag-area'); const tagArea = area.querySelector('.tag-area');
toggleElem(tagArea.parentElement, tags.length > 0); toggleElem(tagArea.parentElement, tags.length > 0);
for (const tag of tags) { for (const tag of tags) {
@ -23,7 +23,7 @@ function addTags(area, tags) {
} }
} }
function addBranches(area, branches, defaultBranch) { function addBranches(area: Element, branches: Array<Record<string, any>>, defaultBranch: string) {
const defaultBranchTooltip = area.getAttribute('data-text-default-branch-tooltip'); const defaultBranchTooltip = area.getAttribute('data-text-default-branch-tooltip');
const branchArea = area.querySelector('.branch-area'); const branchArea = area.querySelector('.branch-area');
toggleElem(branchArea.parentElement, branches.length > 0); toggleElem(branchArea.parentElement, branches.length > 0);
@ -33,7 +33,7 @@ function addBranches(area, branches, defaultBranch) {
} }
} }
function addLink(parent, href, text, tooltip) { function addLink(parent: Element, href: string, text: string, tooltip?: string) {
const link = document.createElement('a'); const link = document.createElement('a');
link.classList.add('muted', 'tw-px-1'); link.classList.add('muted', 'tw-px-1');
link.href = href; link.href = href;

View File

@ -22,7 +22,7 @@ export function initRepoGraphGit() {
for (const link of document.querySelectorAll('.pagination a')) { for (const link of document.querySelectorAll('.pagination a')) {
const href = link.getAttribute('href'); const href = link.getAttribute('href');
if (!href) continue; if (!href) continue;
const url = new URL(href, window.location); const url = new URL(href, window.location.href);
const params = url.searchParams; const params = url.searchParams;
params.set('mode', 'monochrome'); params.set('mode', 'monochrome');
url.search = `?${params.toString()}`; url.search = `?${params.toString()}`;
@ -38,7 +38,7 @@ export function initRepoGraphGit() {
for (const link of document.querySelectorAll('.pagination a')) { for (const link of document.querySelectorAll('.pagination a')) {
const href = link.getAttribute('href'); const href = link.getAttribute('href');
if (!href) continue; if (!href) continue;
const url = new URL(href, window.location); const url = new URL(href, window.location.href);
const params = url.searchParams; const params = url.searchParams;
params.delete('mode'); params.delete('mode');
url.search = `?${params.toString()}`; url.search = `?${params.toString()}`;
@ -53,7 +53,7 @@ export function initRepoGraphGit() {
window.history.replaceState({}, '', window.location.pathname); window.history.replaceState({}, '', window.location.pathname);
} }
}); });
const url = new URL(window.location); const url = new URL(window.location.href);
const params = url.searchParams; const params = url.searchParams;
const updateGraph = () => { const updateGraph = () => {
const queryString = params.toString(); const queryString = params.toString();
@ -103,7 +103,7 @@ export function initRepoGraphGit() {
}, },
onAdd(toAdd) { onAdd(toAdd) {
if (toAdd === '...flow-hide-pr-refs') { if (toAdd === '...flow-hide-pr-refs') {
params.set('hide-pr-refs', true); params.set('hide-pr-refs', 'true');
} else { } else {
params.append('branch', toAdd); params.append('branch', toAdd);
} }
@ -111,7 +111,7 @@ export function initRepoGraphGit() {
}, },
}); });
graphContainer.addEventListener('mouseenter', (e) => { graphContainer.addEventListener('mouseenter', (e: MouseEvent & {target: HTMLElement}) => {
if (e.target.matches('#rev-list li')) { if (e.target.matches('#rev-list li')) {
const flow = e.target.getAttribute('data-flow'); const flow = e.target.getAttribute('data-flow');
if (flow === '0') return; if (flow === '0') return;
@ -132,7 +132,7 @@ export function initRepoGraphGit() {
} }
}); });
graphContainer.addEventListener('mouseleave', (e) => { graphContainer.addEventListener('mouseleave', (e: MouseEvent & {target: HTMLElement}) => {
if (e.target.matches('#rev-list li')) { if (e.target.matches('#rev-list li')) {
const flow = e.target.getAttribute('data-flow'); const flow = e.target.getAttribute('data-flow');
if (flow === '0') return; if (flow === '0') return;

View File

@ -1,7 +1,7 @@
import {stripTags} from '../utils.ts'; import {stripTags} from '../utils.ts';
import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts'; import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts';
import {POST} from '../modules/fetch.ts'; import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts'; import {showErrorToast, type Toast} from '../modules/toast.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts';
const {appSubUrl} = window.config; const {appSubUrl} = window.config;
@ -13,7 +13,7 @@ export function initRepoTopicBar() {
const editDiv = document.querySelector('#topic_edit'); const editDiv = document.querySelector('#topic_edit');
const viewDiv = document.querySelector('#repo-topics'); const viewDiv = document.querySelector('#repo-topics');
const topicDropdown = editDiv.querySelector('.ui.dropdown'); const topicDropdown = editDiv.querySelector('.ui.dropdown');
let lastErrorToast; let lastErrorToast: Toast;
mgrBtn.addEventListener('click', () => { mgrBtn.addEventListener('click', () => {
hideElem(viewDiv); hideElem(viewDiv);

View File

@ -1,7 +1,7 @@
export function initRepoPullRequestCommitStatus() { export function initRepoPullRequestCommitStatus() {
for (const btn of document.querySelectorAll('.commit-status-hide-checks')) { for (const btn of document.querySelectorAll('.commit-status-hide-checks')) {
const panel = btn.closest('.commit-status-panel'); const panel = btn.closest('.commit-status-panel');
const list = panel.querySelector('.commit-status-list'); const list = panel.querySelector<HTMLElement>('.commit-status-list');
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle
btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all');

View File

@ -124,7 +124,7 @@ export function initRepoIssueFilterItemLabel() {
export function initRepoIssueCommentDelete() { export function initRepoIssueCommentDelete() {
// Delete comment // Delete comment
document.addEventListener('click', async (e) => { document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
if (!e.target.matches('.delete-comment')) return; if (!e.target.matches('.delete-comment')) return;
e.preventDefault(); e.preventDefault();
@ -143,7 +143,7 @@ export function initRepoIssueCommentDelete() {
const counter = document.querySelector('#review-box .review-comments-counter'); const counter = document.querySelector('#review-box .review-comments-counter');
let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0; let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
num = Math.max(num, 0); num = Math.max(num, 0);
counter.setAttribute('data-pending-comment-number', num); counter.setAttribute('data-pending-comment-number', String(num));
counter.textContent = String(num); counter.textContent = String(num);
} }
@ -199,7 +199,7 @@ export function initRepoIssueDependencyDelete() {
export function initRepoIssueCodeCommentCancel() { export function initRepoIssueCodeCommentCancel() {
// Cancel inline code comment // Cancel inline code comment
document.addEventListener('click', (e) => { document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
if (!e.target.matches('.cancel-code-comment')) return; if (!e.target.matches('.cancel-code-comment')) return;
const form = e.target.closest('form'); const form = e.target.closest('form');
@ -268,12 +268,14 @@ export function initRepoPullRequestMergeInstruction() {
export function initRepoPullRequestAllowMaintainerEdit() { export function initRepoPullRequestAllowMaintainerEdit() {
const wrapper = document.querySelector('#allow-edits-from-maintainers'); const wrapper = document.querySelector('#allow-edits-from-maintainers');
if (!wrapper) return; if (!wrapper) return;
const checkbox = wrapper.querySelector('input[type="checkbox"]'); const checkbox = wrapper.querySelector<HTMLInputElement>('input[type="checkbox"]');
checkbox.addEventListener('input', async () => { checkbox.addEventListener('input', async () => {
const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`; const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
wrapper.classList.add('is-loading'); wrapper.classList.add('is-loading');
try { try {
const resp = await POST(url, {data: new URLSearchParams({allow_maintainer_edit: checkbox.checked})}); const resp = await POST(url, {data: new URLSearchParams({
allow_maintainer_edit: String(checkbox.checked),
})});
if (!resp.ok) { if (!resp.ok) {
throw new Error('Failed to update maintainer edit permission'); throw new Error('Failed to update maintainer edit permission');
} }
@ -322,7 +324,7 @@ export function initRepoIssueWipTitle() {
const $issueTitle = $('#issue_title'); const $issueTitle = $('#issue_title');
$issueTitle.trigger('focus'); $issueTitle.trigger('focus');
const value = $issueTitle.val().trim().toUpperCase(); const value = ($issueTitle.val() as string).trim().toUpperCase();
const wipPrefixes = $('.title_wip_desc').data('wip-prefixes'); const wipPrefixes = $('.title_wip_desc').data('wip-prefixes');
for (const prefix of wipPrefixes) { for (const prefix of wipPrefixes) {
@ -338,7 +340,7 @@ export function initRepoIssueWipTitle() {
export function initRepoIssueComments() { export function initRepoIssueComments() {
if (!$('.repository.view.issue .timeline').length) return; if (!$('.repository.view.issue .timeline').length) return;
document.addEventListener('click', (e) => { document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
const urlTarget = document.querySelector(':target'); const urlTarget = document.querySelector(':target');
if (!urlTarget) return; if (!urlTarget) return;
@ -490,7 +492,7 @@ export function initRepoPullRequestReview() {
export function initRepoIssueReferenceIssue() { export function initRepoIssueReferenceIssue() {
// Reference issue // Reference issue
$(document).on('click', '.reference-issue', function (event) { $(document).on('click', '.reference-issue', function (e) {
const target = this.getAttribute('data-target'); const target = this.getAttribute('data-target');
const content = document.querySelector(`#${target}`)?.textContent ?? ''; const content = document.querySelector(`#${target}`)?.textContent ?? '';
const poster = this.getAttribute('data-poster-username'); const poster = this.getAttribute('data-poster-username');
@ -500,7 +502,7 @@ export function initRepoIssueReferenceIssue() {
const textarea = modal.querySelector('textarea[name="content"]'); const textarea = modal.querySelector('textarea[name="content"]');
textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`; textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`;
$(modal).modal('show'); $(modal).modal('show');
event.preventDefault(); e.preventDefault();
}); });
} }
@ -584,7 +586,7 @@ export function initRepoIssueTitleEdit() {
} }
export function initRepoIssueBranchSelect() { export function initRepoIssueBranchSelect() {
document.querySelector('#branch-select')?.addEventListener('click', (e) => { document.querySelector('#branch-select')?.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
const el = e.target.closest('.item[data-branch]'); const el = e.target.closest('.item[data-branch]');
if (!el) return; if (!el) return;
const pullTargetBranch = document.querySelector('#pull-target-branch'); const pullTargetBranch = document.querySelector('#pull-target-branch');

View File

@ -8,9 +8,7 @@ import {
} from './repo-issue.ts'; } from './repo-issue.ts';
import {initUnicodeEscapeButton} from './repo-unicode-escape.ts'; import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
import { import {initRepoCloneButtons} from './repo-common.ts';
initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
} from './repo-common.ts';
import {initCitationFileCopyContent} from './citation.ts'; import {initCitationFileCopyContent} from './citation.ts';
import {initCompLabelEdit} from './comp/LabelEdit.ts'; import {initCompLabelEdit} from './comp/LabelEdit.ts';
import {initRepoDiffConversationNav} from './repo-diff.ts'; import {initRepoDiffConversationNav} from './repo-diff.ts';
@ -36,6 +34,33 @@ export function initBranchSelectorTabs() {
}); });
} }
function initRepoCommonBranchOrTagDropdown(selector: string) {
$(selector).each(function () {
const $dropdown = $(this);
$dropdown.find('.reference.column').on('click', function () {
hideElem($dropdown.find('.scrolling.reference-list-menu'));
showElem($($(this).data('target')));
return false;
});
});
}
function initRepoCommonFilterSearchDropdown(selector: string) {
const $dropdown = $(selector);
if (!$dropdown.length) return;
$dropdown.dropdown({
fullTextSearch: 'exact',
selectOnKeydown: false,
onChange(_text, _value, $choice) {
if ($choice[0].getAttribute('data-url')) {
window.location.href = $choice[0].getAttribute('data-url');
}
},
message: {noResults: $dropdown[0].getAttribute('data-no-results')},
});
}
export function initRepository() { export function initRepository() {
if (!$('.page-content.repository').length) return; if (!$('.page-content.repository').length) return;
@ -54,7 +79,7 @@ export function initRepository() {
initRepoCommonFilterSearchDropdown('.choose.branch .dropdown'); initRepoCommonFilterSearchDropdown('.choose.branch .dropdown');
} }
initRepoCloneLink(); initRepoCloneButtons();
initCitationFileCopyContent(); initCitationFileCopyContent();
initRepoSettings(); initRepoSettings();

View File

@ -1,14 +1,14 @@
import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
const service = document.querySelector('#service_type'); const service = document.querySelector<HTMLInputElement>('#service_type');
const user = document.querySelector('#auth_username'); const user = document.querySelector<HTMLInputElement>('#auth_username');
const pass = document.querySelector('#auth_password'); const pass = document.querySelector<HTMLInputElement>('#auth_password');
const token = document.querySelector('#auth_token'); const token = document.querySelector<HTMLInputElement>('#auth_token');
const mirror = document.querySelector('#mirror'); const mirror = document.querySelector<HTMLInputElement>('#mirror');
const lfs = document.querySelector('#lfs'); const lfs = document.querySelector<HTMLInputElement>('#lfs');
const lfsSettings = document.querySelector('#lfs_settings'); const lfsSettings = document.querySelector<HTMLElement>('#lfs_settings');
const lfsEndpoint = document.querySelector('#lfs_endpoint'); const lfsEndpoint = document.querySelector<HTMLElement>('#lfs_endpoint');
const items = document.querySelectorAll('#migrate_items input[type=checkbox]'); const items = document.querySelectorAll<HTMLInputElement>('#migrate_items input[type=checkbox]');
export function initRepoMigration() { export function initRepoMigration() {
checkAuth(); checkAuth();
@ -25,11 +25,11 @@ export function initRepoMigration() {
}); });
lfs?.addEventListener('change', setLFSSettingsVisibility); lfs?.addEventListener('change', setLFSSettingsVisibility);
const cloneAddr = document.querySelector('#clone_addr'); const cloneAddr = document.querySelector<HTMLInputElement>('#clone_addr');
cloneAddr?.addEventListener('change', () => { cloneAddr?.addEventListener('change', () => {
const repoName = document.querySelector('#repo_name'); const repoName = document.querySelector<HTMLInputElement>('#repo_name');
if (cloneAddr.value && !repoName?.value) { // Only modify if repo_name input is blank if (cloneAddr.value && !repoName?.value) { // Only modify if repo_name input is blank
repoName.value = cloneAddr.value.match(/^(.*\/)?((.+?)(\.git)?)$/)[3]; repoName.value = /^(.*\/)?((.+?)(\.git)?)$/.exec(cloneAddr.value)[3];
} }
}); });
} }
@ -41,8 +41,8 @@ function checkAuth() {
checkItems(serviceType !== 1); checkItems(serviceType !== 1);
} }
function checkItems(tokenAuth) { function checkItems(tokenAuth: boolean) {
let enableItems; let enableItems = false;
if (tokenAuth) { if (tokenAuth) {
enableItems = token?.value !== ''; enableItems = token?.value !== '';
} else { } else {

View File

@ -7,7 +7,7 @@ export function initRepoNew() {
const gitignores = $('input[name="gitignores"]').val(); const gitignores = $('input[name="gitignores"]').val();
const license = $('input[name="license"]').val(); const license = $('input[name="license"]').val();
if (gitignores || license) { if (gitignores || license) {
document.querySelector('input[name="auto_init"]').checked = true; document.querySelector<HTMLInputElement>('input[name="auto_init"]').checked = true;
} }
}); });
} }

View File

@ -25,7 +25,7 @@ async function createNewColumn(url, columnTitle, projectColorInput) {
} }
} }
async function moveIssue({item, from, to, oldIndex}) { async function moveIssue({item, from, to, oldIndex}: {item: HTMLElement, from: HTMLElement, to: HTMLElement, oldIndex: number}) {
const columnCards = to.querySelectorAll('.issue-card'); const columnCards = to.querySelectorAll('.issue-card');
updateIssueCount(from); updateIssueCount(from);
updateIssueCount(to); updateIssueCount(to);
@ -97,14 +97,14 @@ export function initRepoProject() {
return; return;
} }
const _promise = initRepoProjectSortable(); initRepoProjectSortable(); // no await
for (const modal of document.querySelectorAll('.edit-project-column-modal')) { for (const modal of document.querySelectorAll('.edit-project-column-modal')) {
const projectHeader = modal.closest('.project-column-header'); const projectHeader = modal.closest<HTMLElement>('.project-column-header');
const projectTitleLabel = projectHeader?.querySelector('.project-column-title-label'); const projectTitleLabel = projectHeader?.querySelector<HTMLElement>('.project-column-title-label');
const projectTitleInput = modal.querySelector('.project-column-title-input'); const projectTitleInput = modal.querySelector<HTMLInputElement>('.project-column-title-input');
const projectColorInput = modal.querySelector('#new_project_column_color'); const projectColorInput = modal.querySelector<HTMLInputElement>('#new_project_column_color');
const boardColumn = modal.closest('.project-column'); const boardColumn = modal.closest<HTMLElement>('.project-column');
modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) { modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) {
e.preventDefault(); e.preventDefault();
try { try {
@ -119,7 +119,7 @@ export function initRepoProject() {
} finally { } finally {
projectTitleLabel.textContent = projectTitleInput?.value; projectTitleLabel.textContent = projectTitleInput?.value;
projectTitleInput.closest('form')?.classList.remove('dirty'); projectTitleInput.closest('form')?.classList.remove('dirty');
const dividers = boardColumn.querySelectorAll(':scope > .divider'); const dividers = boardColumn.querySelectorAll<HTMLElement>(':scope > .divider');
if (projectColorInput.value) { if (projectColorInput.value) {
const color = contrastColor(projectColorInput.value); const color = contrastColor(projectColorInput.value);
boardColumn.style.setProperty('background', projectColorInput.value, 'important'); boardColumn.style.setProperty('background', projectColorInput.value, 'important');

View File

@ -1,11 +1,11 @@
import {hideElem, showElem} from '../utils/dom.ts'; import {hideElem, showElem} from '../utils/dom.ts';
export function initRepoRelease() { export function initRepoRelease() {
document.addEventListener('click', (e) => { document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
if (e.target.matches('.remove-rel-attach')) { if (e.target.matches('.remove-rel-attach')) {
const uuid = e.target.getAttribute('data-uuid'); const uuid = e.target.getAttribute('data-uuid');
const id = e.target.getAttribute('data-id'); const id = e.target.getAttribute('data-id');
document.querySelector(`input[name='attachment-del-${uuid}']`).value = 'true'; document.querySelector<HTMLInputElement>(`input[name='attachment-del-${uuid}']`).value = 'true';
hideElem(`#attachment-${id}`); hideElem(`#attachment-${id}`);
} }
}); });
@ -28,8 +28,8 @@ function initTagNameEditor() {
const newTagHelperText = el.getAttribute('data-tag-helper-new'); const newTagHelperText = el.getAttribute('data-tag-helper-new');
const existingTagHelperText = el.getAttribute('data-tag-helper-existing'); const existingTagHelperText = el.getAttribute('data-tag-helper-existing');
const tagNameInput = document.querySelector('#tag-name'); const tagNameInput = document.querySelector<HTMLInputElement>('#tag-name');
const hideTargetInput = function(tagNameInput) { const hideTargetInput = function(tagNameInput: HTMLInputElement) {
const value = tagNameInput.value; const value = tagNameInput.value;
const tagHelper = document.querySelector('#tag-helper'); const tagHelper = document.querySelector('#tag-helper');
if (existingTags.includes(value)) { if (existingTags.includes(value)) {
@ -42,7 +42,7 @@ function initTagNameEditor() {
} }
}; };
hideTargetInput(tagNameInput); // update on page load because the input may have a value hideTargetInput(tagNameInput); // update on page load because the input may have a value
tagNameInput.addEventListener('input', (e) => { tagNameInput.addEventListener('input', (e: InputEvent & {target: HTMLInputElement}) => {
hideTargetInput(e.target); hideTargetInput(e.target);
}); });
} }

View File

@ -1,8 +1,8 @@
export function initRepositorySearch() { export function initRepositorySearch() {
const repositorySearchForm = document.querySelector('#repo-search-form'); const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form');
if (!repositorySearchForm) return; if (!repositorySearchForm) return;
repositorySearchForm.addEventListener('change', (e) => { repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => {
e.preventDefault(); e.preventDefault();
const formData = new FormData(repositorySearchForm); const formData = new FormData(repositorySearchForm);

View File

@ -73,7 +73,7 @@ function initRepoSettingsSearchTeamBox() {
function initRepoSettingsGitHook() { function initRepoSettingsGitHook() {
if (!$('.edit.githook').length) return; if (!$('.edit.githook').length) return;
const filename = document.querySelector('.hook-filename').textContent; const filename = document.querySelector('.hook-filename').textContent;
createMonaco($('#content')[0], filename, {language: 'shell'}); createMonaco($('#content')[0] as HTMLTextAreaElement, filename, {language: 'shell'});
} }
function initRepoSettingsBranches() { function initRepoSettingsBranches() {
@ -99,7 +99,7 @@ function initRepoSettingsBranches() {
// show the `Matched` mark for the status checks that match the pattern // show the `Matched` mark for the status checks that match the pattern
const markMatchedStatusChecks = () => { const markMatchedStatusChecks = () => {
const patterns = (document.querySelector('#status_check_contexts').value || '').split(/[\r\n]+/); const patterns = (document.querySelector<HTMLTextAreaElement>('#status_check_contexts').value || '').split(/[\r\n]+/);
const validPatterns = patterns.map((item) => item.trim()).filter(Boolean); const validPatterns = patterns.map((item) => item.trim()).filter(Boolean);
const marks = document.querySelectorAll('.status-check-matched-mark'); const marks = document.querySelectorAll('.status-check-matched-mark');
@ -122,7 +122,7 @@ function initRepoSettingsBranches() {
function initRepoSettingsOptions() { function initRepoSettingsOptions() {
if ($('.repository.settings.options').length > 0) { if ($('.repository.settings.options').length > 0) {
// Enable or select internal/external wiki system and issue tracker. // Enable or select internal/external wiki system and issue tracker.
$('.enable-system').on('change', function () { $('.enable-system').on('change', function (this: HTMLInputElement) {
if (this.checked) { if (this.checked) {
$($(this).data('target')).removeClass('disabled'); $($(this).data('target')).removeClass('disabled');
if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
@ -131,7 +131,7 @@ function initRepoSettingsOptions() {
if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
} }
}); });
$('.enable-system-radio').on('change', function () { $('.enable-system-radio').on('change', function (this: HTMLInputElement) {
if (this.value === 'false') { if (this.value === 'false') {
$($(this).data('target')).addClass('disabled'); $($(this).data('target')).addClass('disabled');
if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled'); if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');

View File

@ -2,7 +2,7 @@ export function initSshKeyFormParser() {
// Parse SSH Key // Parse SSH Key
document.querySelector('#ssh-key-content')?.addEventListener('input', function () { document.querySelector('#ssh-key-content')?.addEventListener('input', function () {
const arrays = this.value.split(' '); const arrays = this.value.split(' ');
const title = document.querySelector('#ssh-key-title'); const title = document.querySelector<HTMLInputElement>('#ssh-key-title');
if (!title.value && arrays.length === 3 && arrays[2] !== '') { if (!title.value && arrays.length === 3 && arrays[2] !== '') {
title.value = arrays[2]; title.value = arrays[2];
} }

View File

@ -13,7 +13,7 @@ function tableSort(normSort, revSort, isDefault) {
if (!normSort) return false; if (!normSort) return false;
if (!revSort) revSort = ''; if (!revSort) revSort = '';
const url = new URL(window.location); const url = new URL(window.location.href);
let urlSort = url.searchParams.get('sort'); let urlSort = url.searchParams.get('sort');
if (!urlSort && isDefault) urlSort = normSort; if (!urlSort && isDefault) urlSort = normSort;

View File

@ -48,7 +48,7 @@ function makeCollections({mentions, emoji}) {
return collections; return collections;
} }
export async function attachTribute(element, {mentions, emoji} = {}) { export async function attachTribute(element, {mentions, emoji}) {
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs'); const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
const collections = makeCollections({mentions, emoji}); const collections = makeCollections({mentions, emoji});
const tribute = new Tribute({collection: collections, noMatchTemplate: ''}); const tribute = new Tribute({collection: collections, noMatchTemplate: ''});

View File

@ -33,6 +33,7 @@ interface JQuery {
modal: any; // fomantic modal: any; // fomantic
tab: any; // fomantic tab: any; // fomantic
transition: any, // fomantic transition: any, // fomantic
search: any, // fomantic
} }
interface JQueryStatic { interface JQueryStatic {
@ -62,4 +63,5 @@ interface Window {
turnstile: any, turnstile: any,
hcaptcha: any, hcaptcha: any,
codeEditors: any[], codeEditors: any[],
updateCloneStates: () => void,
} }

View File

@ -5,6 +5,9 @@ import Toastify from 'toastify-js'; // don't use "async import", because when ne
import type {Intent} from '../types.ts'; import type {Intent} from '../types.ts';
import type {SvgName} from '../svg.ts'; import type {SvgName} from '../svg.ts';
import type {Options} from 'toastify-js'; import type {Options} from 'toastify-js';
import type StartToastifyInstance from 'toastify-js';
export type Toast = ReturnType<typeof StartToastifyInstance>;
type ToastLevels = { type ToastLevels = {
[intent in Intent]: { [intent in Intent]: {
@ -38,7 +41,7 @@ type ToastOpts = {
} & Options; } & Options;
// See https://github.com/apvarun/toastify-js#api for options // See https://github.com/apvarun/toastify-js#api for options
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}) { function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast {
const body = useHtmlBody ? String(message) : htmlEscape(message); const body = useHtmlBody ? String(message) : htmlEscape(message);
const key = `${level}-${body}`; const key = `${level}-${body}`;
@ -75,14 +78,14 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
return toast; return toast;
} }
export function showInfoToast(message: string, opts?: ToastOpts) { export function showInfoToast(message: string, opts?: ToastOpts): Toast {
return showToast(message, 'info', opts); return showToast(message, 'info', opts);
} }
export function showWarningToast(message: string, opts?: ToastOpts) { export function showWarningToast(message: string, opts?: ToastOpts): Toast {
return showToast(message, 'warning', opts); return showToast(message, 'warning', opts);
} }
export function showErrorToast(message: string, opts?: ToastOpts) { export function showErrorToast(message: string, opts?: ToastOpts): Toast {
return showToast(message, 'error', opts); return showToast(message, 'error', opts);
} }

View File

@ -1,4 +1,4 @@
import {pathEscapeSegments, isUrl} from './url.ts'; import {pathEscapeSegments, isUrl, toOriginUrl} from './url.ts';
test('pathEscapeSegments', () => { test('pathEscapeSegments', () => {
expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c'); expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c');
@ -11,3 +11,19 @@ test('isUrl', () => {
expect(isUrl('https://example.com/index.html')).toEqual(true); expect(isUrl('https://example.com/index.html')).toEqual(true);
expect(isUrl('/index.html')).toEqual(false); expect(isUrl('/index.html')).toEqual(false);
}); });
test('toOriginUrl', () => {
const oldLocation = String(window.location);
for (const origin of ['https://example.com', 'https://example.com:3000']) {
window.location.assign(`${origin}/`);
expect(toOriginUrl('/')).toEqual(`${origin}/`);
expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`);
}
window.location.assign(oldLocation);
});

View File

@ -13,3 +13,19 @@ export function isUrl(url: string): boolean {
return false; return false;
} }
} }
// Convert an absolute or relative URL to an absolute URL with the current origin. It only
// processes absolute HTTP/HTTPS URLs or relative URLs like '/xxx' or '//host/xxx'.
export function toOriginUrl(urlStr: string) {
try {
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const {origin, protocol, hostname, port} = window.location;
const url = new URL(urlStr, origin);
url.protocol = protocol;
url.hostname = hostname;
url.port = port || (protocol === 'https:' ? '443' : '80');
return url.toString();
}
} catch {}
return urlStr;
}

View File

@ -1,17 +0,0 @@
import {toOriginUrl} from './origin-url.ts';
test('toOriginUrl', () => {
const oldLocation = String(window.location);
for (const origin of ['https://example.com', 'https://example.com:3000']) {
window.location.assign(`${origin}/`);
expect(toOriginUrl('/')).toEqual(`${origin}/`);
expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`);
}
window.location.assign(oldLocation);
});

View File

@ -1,19 +1,4 @@
// Convert an absolute or relative URL to an absolute URL with the current origin. It only import {toOriginUrl} from '../utils/url.ts';
// processes absolute HTTP/HTTPS URLs or relative URLs like '/xxx' or '//host/xxx'.
// NOTE: Keep this function in sync with clone_script.tmpl
export function toOriginUrl(urlStr: string) {
try {
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const {origin, protocol, hostname, port} = window.location;
const url = new URL(urlStr, origin);
url.protocol = protocol;
url.hostname = hostname;
url.port = port || (protocol === 'https:' ? '443' : '80');
return url.toString();
}
} catch {}
return urlStr;
}
window.customElements.define('origin-url', class extends HTMLElement { window.customElements.define('origin-url', class extends HTMLElement {
connectedCallback() { connectedCallback() {