diff --git a/go.mod b/go.mod index 80b62ce83f8..17be4cbd526 100644 --- a/go.mod +++ b/go.mod @@ -121,13 +121,13 @@ require ( github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc 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/net v0.30.0 golang.org/x/oauth2 v0.23.0 - golang.org/x/sync v0.8.0 - golang.org/x/sys v0.26.0 - golang.org/x/text v0.19.0 + golang.org/x/sync v0.10.0 + golang.org/x/sys v0.28.0 + golang.org/x/text v0.21.0 golang.org/x/tools v0.26.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 diff --git a/go.sum b/go.sum index d1b7890fb68..73bdb44e337 100644 --- a/go.sum +++ b/go.sum @@ -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.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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.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/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= 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.6.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.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-20181205085412-a5c9d58dba9a/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.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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 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/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= @@ -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.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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= 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.3/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.14.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.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/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index 7ef370e89c5..bf51bd6c148 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -23,7 +23,7 @@ import ( const ( issueIndexerAnalyzer = "issueIndexer" issueIndexerDocType = "issueIndexerDocType" - issueIndexerLatestVersion = 4 + issueIndexerLatestVersion = 5 ) const unicodeNormalizeName = "unicodeNormalize" @@ -75,6 +75,7 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) { docMapping.AddFieldMappingsAt("is_pull", boolFieldMapping) docMapping.AddFieldMappingsAt("is_closed", boolFieldMapping) + docMapping.AddFieldMappingsAt("is_archived", boolFieldMapping) docMapping.AddFieldMappingsAt("label_ids", numberFieldMapping) docMapping.AddFieldMappingsAt("no_label", boolFieldMapping) docMapping.AddFieldMappingsAt("milestone_id", numberFieldMapping) @@ -185,6 +186,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.IsClosed.Has() { 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 { queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_label")) diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 98b097f8713..42834f6e886 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -72,7 +72,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m UpdatedAfterUnix: options.UpdatedAfterUnix.Value(), UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(), PriorityRepoID: 0, - IsArchived: optional.None[bool](), + IsArchived: options.IsArchived, Org: nil, Team: nil, User: nil, diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go index 1a0f241e615..4f6ad96d222 100644 --- a/modules/indexer/issues/dboptions.go +++ b/modules/indexer/issues/dboptions.go @@ -11,11 +11,12 @@ import ( func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions { searchOpt := &SearchOptions{ - Keyword: keyword, - RepoIDs: opts.RepoIDs, - AllPublic: opts.AllPublic, - IsPull: opts.IsPull, - IsClosed: opts.IsClosed, + Keyword: keyword, + RepoIDs: opts.RepoIDs, + AllPublic: opts.AllPublic, + IsPull: opts.IsPull, + IsClosed: opts.IsClosed, + IsArchived: opts.IsArchived, } if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 { diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 6f705150097..4c293f3f2a9 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -18,7 +18,7 @@ import ( ) const ( - issueIndexerLatestVersion = 1 + issueIndexerLatestVersion = 2 // 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 esMultiMatchTypeBestFields = "best_fields" @@ -58,6 +58,7 @@ const ( "is_pull": { "type": "boolean", "index": true }, "is_closed": { "type": "boolean", "index": true }, + "is_archived": { "type": "boolean", "index": true }, "label_ids": { "type": "integer", "index": true }, "no_label": { "type": "boolean", "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() { 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 { query.Must(elastic.NewTermQuery("no_label", true)) diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index 7c3ba75bb07..06a6a46c234 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -37,6 +37,7 @@ func TestDBSearchIssues(t *testing.T) { t.Run("search issues by ID", searchIssueByID) t.Run("search issues is pr", searchIssueIsPull) 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 label", searchIssueByLabelID) 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) { tests := []struct { opts SearchOptions diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index a43c6be0059..09dcbf4804c 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -25,6 +25,7 @@ type IndexerData struct { // Fields used for filtering IsPull bool `json:"is_pull"` IsClosed bool `json:"is_closed"` + IsArchived bool `json:"is_archived"` LabelIDs []int64 `json:"label_ids"` NoLabel bool `json:"no_label"` // True if LabelIDs is empty MilestoneID int64 `json:"milestone_id"` @@ -81,8 +82,9 @@ type SearchOptions struct { RepoIDs []int64 // repository IDs which the issues belong to AllPublic bool // if include all public repositories - IsPull optional.Option[bool] // if the issues is a pull request - IsClosed optional.Option[bool] // if the issues is closed + IsPull optional.Option[bool] // if the issues is a pull request + IsClosed optional.Option[bool] // if the issues is closed + IsArchived optional.Option[bool] // if the repo is archived IncludedLabelIDs []int64 // labels the issues have ExcludedLabelIDs []int64 // labels the issues don't have diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 93323193392..1066e962725 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -18,7 +18,7 @@ import ( ) const ( - issueIndexerLatestVersion = 3 + issueIndexerLatestVersion = 4 // TODO: make this configurable if necessary maxTotalHits = 10000 @@ -61,6 +61,7 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer { "is_public", "is_pull", "is_closed", + "is_archived", "label_ids", "no_label", "milestone_id", @@ -145,6 +146,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.IsClosed.Has() { 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 { query.And(inner_meilisearch.NewFilterEq("no_label", true)) diff --git a/modules/indexer/issues/util.go b/modules/indexer/issues/util.go index e752ae6f243..deb19adc49d 100644 --- a/modules/indexer/issues/util.go +++ b/modules/indexer/issues/util.go @@ -101,6 +101,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD Comments: comments, IsPull: issue.IsPull, IsClosed: issue.IsClosed, + IsArchived: issue.Repo.IsArchived, LabelIDs: labels, NoLabel: len(labels) == 0, MilestoneID: issue.MilestoneID, diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 4e194f65fa1..47ea7137b82 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -358,6 +358,7 @@ func CommonRoutes() *web.Router { r.Get("/PACKAGES", cran.EnumerateSourcePackages) r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages) r.Get("/{filename}", cran.DownloadSourcePackageFile) + r.Get("/Archive/{packagename}/{filename}", cran.DownloadSourcePackageFile) }) r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadSourcePackageFile) }) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index e30129bb44c..717d7cbce1d 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "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/lfs" "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) } + // update issue indexer + issue_indexer.UpdateRepoIndexer(ctx, repo.ID) + ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) 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")) log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index e0539f53b0c..b318c4a621a 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -74,9 +74,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) { schema, _, _ := strings.Cut(app.OpenURL, ":") var iconHTML template.HTML 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 { - 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{ "DisplayName": app.DisplayName, diff --git a/templates/repo/clone_buttons.tmpl b/templates/repo/clone_buttons.tmpl index 91952c8a06d..03b7a561daa 100644 --- a/templates/repo/clone_buttons.tmpl +++ b/templates/repo/clone_buttons.tmpl @@ -1,15 +1,13 @@ - -{{if $.CloneButtonShowHTTPS}} - + {{end}} + {{if $.CloneButtonShowSSH}} + + {{end}} + + -{{end}} -{{if $.CloneButtonShowSSH}} - -{{end}} - - + diff --git a/templates/repo/clone_panel.tmpl b/templates/repo/clone_panel.tmpl new file mode 100644 index 00000000000..8cbeda132dd --- /dev/null +++ b/templates/repo/clone_panel.tmpl @@ -0,0 +1,44 @@ + +
+
{{svg "octicon-terminal"}} Clone
+ +
+ + {{if $.CloneButtonShowHTTPS}} + + {{end}} + {{if $.CloneButtonShowSSH}} + + {{end}} +
+
+ +
+
+ +
+ {{svg "octicon-copy" 14}} +
+
+
+ + {{if not .PageIsWiki}} +
+ {{range .OpenWithEditorApps}} + {{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}} + {{end}} +
+ + {{if and (not $.DisableDownloadSourceArchives) $.RefName}} +
+
+ {{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_zip"}} + {{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_tar"}} + {{svg "octicon-package"}} {{ctx.Locale.Tr "repo.download_bundle"}} +
+ {{end}} + {{end}} +
diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl deleted file mode 100644 index 40dae76dc71..00000000000 --- a/templates/repo/clone_script.tmpl +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl index d3a81bc51d2..7170fe36020 100644 --- a/templates/repo/empty.tmpl +++ b/templates/repo/empty.tmpl @@ -37,9 +37,7 @@ {{end}} {{end}} -
- {{template "repo/clone_buttons" .}} -
+ {{template "repo/clone_buttons" .}} @@ -73,7 +71,6 @@ git push -u origin {{.Repository.DefaultBranch}} {{ctx.Locale.Tr "repo.empty_message"}} {{end}} - {{template "repo/clone_script" .}} diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 46d0398c210..4e6d375b517 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -102,27 +102,10 @@ {{end}} - {{/* 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 */}} -
+
{{if $isTreePathRoot}} -
- {{template "repo/clone_buttons" .}} - - {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} -
+ {{template "repo/clone_panel" .}} {{end}} {{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}} @@ -140,6 +123,9 @@ {{template "repo/code/upstream_diverging_info" .}} {{end}} {{template "repo/view_list" .}} + {{if and .ReadmeExist (or .IsMarkup .IsPlainText)}} + {{template "repo/view_file" .}} + {{end}} {{end}}
diff --git a/templates/repo/latest_commit.tmpl b/templates/repo/latest_commit.tmpl index 9d718d7197a..34a5df8f776 100644 --- a/templates/repo/latest_commit.tmpl +++ b/templates/repo/latest_commit.tmpl @@ -1,5 +1,5 @@ {{if not .LatestCommit}} -
+ … {{else}} {{if .LatestCommitUser}} {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 3edfbb34745..ea61c3736ae 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -1,73 +1,57 @@ - - - - - - - - - {{if .HasParentPath}} - - - - {{end}} - {{range $item := .Files}} +{{/* use grid layout, still use the old ID because there are many other CSS styles depending on this ID */}} +
+
+
{{template "repo/latest_commit" .}}
+
{{if and .LatestCommit .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}
+
+ {{if .HasParentPath}} +
+ {{svg "octicon-reply"}} .. +
+ {{end}} + {{range $item := .Files}} +
{{$entry := $item.Entry}} {{$commit := $item.Commit}} {{$subModuleFile := $item.SubModuleFile}} -
- - - - - {{end}} - -
-
-
- {{template "repo/latest_commit" .}} -
-
-
{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}{{end}}
{{svg "octicon-reply"}}..
- - {{if $entry.IsSubModule}} - {{svg "octicon-file-submodule"}} - {{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} - {{if $refURL}} - {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} +
+ {{if $entry.IsSubModule}} + {{svg "octicon-file-submodule"}} + {{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} + {{if $refURL}} + {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} + {{else}} + {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} + {{end}} + {{else}} + {{if $entry.IsDir}} + {{$subJumpablePathName := $entry.GetSubJumpablePathName}} + {{svg "octicon-file-directory-fill"}} + + {{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}} + {{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}} + {{if eq $subJumpablePathFieldLast 0}} + {{$subJumpablePathName}} {{else}} - {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} + {{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}} + {{StringUtils.Join $subJumpablePathPrefixes "/"}}/{{index $subJumpablePathFields $subJumpablePathFieldLast}} {{end}} - {{else}} - {{if $entry.IsDir}} - {{$subJumpablePathName := $entry.GetSubJumpablePathName}} - {{svg "octicon-file-directory-fill"}} - - {{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}} - {{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}} - {{if eq $subJumpablePathFieldLast 0}} - {{$subJumpablePathName}} - {{else}} - {{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}} - {{StringUtils.Join $subJumpablePathPrefixes "/"}}/{{index $subJumpablePathFields $subJumpablePathFieldLast}} - {{end}} - - {{else}} - {{svg (printf "octicon-%s" (EntryIcon $entry))}} - {{$entry.Name}} - {{end}} - {{end}} - -
- - {{if $commit}} - {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}} - {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}} - {{else}} -
- {{end}} -
-
{{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}
-{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}} - {{template "repo/view_file" .}} -{{end}} + + {{else}} + {{svg (printf "octicon-%s" (EntryIcon $entry))}} + {{$entry.Name}} + {{end}} + {{end}} +
+
+ {{if $commit}} + {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}} + {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}} + {{else}} + … {{/* will be loaded again by LastCommitLoaderURL */}} + {{end}} +
+
{{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}
+ + {{end}} + diff --git a/templates/repo/wiki/revision.tmpl b/templates/repo/wiki/revision.tmpl index 045cc41d81e..ca8954928d2 100644 --- a/templates/repo/wiki/revision.tmpl +++ b/templates/repo/wiki/revision.tmpl @@ -15,10 +15,7 @@
-
- {{template "repo/clone_buttons" .}} - {{template "repo/clone_script" .}} -
+ {{template "repo/clone_panel" .}}

{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}

diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index c8e0b4254ca..2bb0a4f0068 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -4,7 +4,7 @@ {{$title := .title}}
-
+
-
- {{template "repo/clone_buttons" .}} - {{template "repo/clone_script" .}} -
+ {{template "repo/clone_panel" .}}
@@ -45,7 +42,7 @@
-
+
{{if .EscapeStatus.Escaped}} {{ctx.Locale.Tr "repo.unescape_control_characters"}} {{ctx.Locale.Tr "repo.escape_control_characters"}} diff --git a/tests/integration/api_packages_cran_test.go b/tests/integration/api_packages_cran_test.go index d307e87d4e0..667ba0908ce 100644 --- a/tests/integration/api_packages_cran_test.go +++ b/tests/integration/api_packages_cran_test.go @@ -115,6 +115,14 @@ func TestPackageCran(t *testing.T) { 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) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index b967ccad1ec..7889dfaf3b7 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -49,7 +49,7 @@ func testViewRepo(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) 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 { fileName string @@ -61,7 +61,7 @@ func testViewRepo(t *testing.T) { var items []file files.Each(func(i int, s *goquery.Selection) { - tds := s.Find("td") + tds := s.Find(".repo-file-cell") var f file tds.Each(func(i int, s *goquery.Selection) { if i == 0 { @@ -127,10 +127,10 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) 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.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) } @@ -143,10 +143,10 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) 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.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") sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port) assert.Equal(t, sshURL, link) @@ -161,7 +161,7 @@ func TestViewRepoWithSymlinks(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) 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 { cls, _ := s.Find("SVG").Attr("class") file := strings.Trim(s.Find("A").Text(), " \t\n") diff --git a/web_src/css/index.css b/web_src/css/index.css index 158ae42d3e3..02513aebc1c 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -66,7 +66,10 @@ @import "./repo/wiki.css"; @import "./repo/header.css"; @import "./repo/home.css"; +@import "./repo/home-file-list.css"; @import "./repo/reactions.css"; +@import "./repo/clone.css"; +@import "./repo/commit-sign.css"; @import "./editor/fileeditor.css"; @import "./editor/combomarkdowneditor.css"; diff --git a/web_src/css/repo.css b/web_src/css/repo.css index f5785c41a77..9a43e10e826 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -101,42 +101,6 @@ 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 { font-size: 16px; margin-bottom: 5px; @@ -177,138 +141,6 @@ td .commit-summary { 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 { font-size: 1em; } @@ -787,47 +619,6 @@ td .commit-summary { 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 { float: right; margin-right: 8px; @@ -1162,151 +953,6 @@ td .commit-summary { 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 { width: 100%; } @@ -1615,14 +1261,6 @@ td .commit-summary { 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 { display: flex; flex-direction: column; @@ -1678,92 +1316,6 @@ td .commit-summary { 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 { flex: auto; } @@ -1782,7 +1334,7 @@ td .commit-summary { .repository .repository-summary .sub-menu .item { 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); display: flex; align-items: center; @@ -2110,26 +1662,18 @@ td .commit-summary { display: flex; align-items: center; gap: 8px; - justify-content: space-between; + flex-wrap: wrap; } .repo-button-row-left, .repo-button-row-right { display: flex; - flex: 1; align-items: center; gap: 0.5rem; } -.repo-button-row-right { - justify-content: flex-end; -} - -@media (max-width: 1200px) { - .repository:not(.wiki) .repo-button-row { - flex-direction: column; - align-items: stretch; - } +.repo-button-row-left { + flex: 1; } .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 */ } -.repo-button-row input { - height: 30px; -} - -@media (max-width: 600px) { - .repo-button-row-left { - flex-wrap: wrap; - } -} - tbody.commit-list { vertical-align: baseline; } @@ -2178,11 +1712,6 @@ tbody.commit-list { 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) { tr.commit-list { width: 100%; @@ -2578,25 +2107,6 @@ tbody.commit-list { } @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-item { margin-left: 0; diff --git a/web_src/css/repo/clone.css b/web_src/css/repo/clone.css new file mode 100644 index 00000000000..15709a78f65 --- /dev/null +++ b/web_src/css/repo/clone.css @@ -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; +} diff --git a/web_src/css/repo/commit-sign.css b/web_src/css/repo/commit-sign.css new file mode 100644 index 00000000000..e7570304194 --- /dev/null +++ b/web_src/css/repo/commit-sign.css @@ -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; +} diff --git a/web_src/css/repo/home-file-list.css b/web_src/css/repo/home-file-list.css new file mode 100644 index 00000000000..eab2124d6f4 --- /dev/null +++ b/web_src/css/repo/home-file-list.css @@ -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; + } +} diff --git a/web_src/css/repo/wiki.css b/web_src/css/repo/wiki.css index ba502d32161..ca59dadb9c8 100644 --- a/web_src/css/repo/wiki.css +++ b/web_src/css/repo/wiki.css @@ -59,9 +59,6 @@ } @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-sidebar { float: none; diff --git a/web_src/js/features/repo-branch.ts b/web_src/js/features/repo-branch.ts index edf2f696387..0c1077748be 100644 --- a/web_src/js/features/repo-branch.ts +++ b/web_src/js/features/repo-branch.ts @@ -11,7 +11,7 @@ function initRepoCreateBranchButton() { for (const el of document.querySelectorAll('.show-create-branch-modal')) { el.addEventListener('click', () => { const modalFormName = el.getAttribute('data-modal-form') || '#create-branch-form'; - const modalForm = document.querySelector(modalFormName); + const modalForm = document.querySelector(modalFormName); if (!modalForm) return; 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 modal = document.querySelector(target); const oldBranchName = el.getAttribute('data-old-branch-name'); - modal.querySelector('input[name=from]').value = oldBranchName; + modal.querySelector('input[name=from]').value = oldBranchName; // display the warning that the branch which is chosen is the default branch const warn = modal.querySelector('.default-branch-warning'); diff --git a/web_src/js/features/repo-code.ts b/web_src/js/features/repo-code.ts index 0068d6c0b52..a8d6e8f97dd 100644 --- a/web_src/js/features/repo-code.ts +++ b/web_src/js/features/repo-code.ts @@ -8,7 +8,7 @@ import {toAbsoluteUrl} from '../utils.ts'; export const singleAnchorRegex = /^#(L|n)([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) { window.history.pushState(null, null, hash); } else { @@ -24,7 +24,7 @@ function getLineEls() { 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) { el.closest('tr').classList.remove('active'); } @@ -34,7 +34,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { const copyPermalink = document.querySelector('a.copy-line-permalink'); const viewGitBlame = document.querySelector('a.view_git_blame'); - const updateIssueHref = function (anchor) { + const updateIssueHref = function (anchor: string) { if (!refInNewIssue) return; const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new'); 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)}`); }; - const updateViewGitBlameFragment = function (anchor) { + const updateViewGitBlameFragment = function (anchor: string) { if (!viewGitBlame) return; let href = viewGitBlame.getAttribute('href'); href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`; @@ -52,7 +52,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { viewGitBlame.setAttribute('href', href); }; - const updateCopyPermalinkUrl = function (anchor) { + const updateCopyPermalinkUrl = function (anchor: string) { if (!copyPermalink) return; let link = copyPermalink.getAttribute('data-url'); link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`; @@ -142,13 +142,7 @@ export function initRepoCodeView() { }); } selectRange($(linesEls), $(selectedEls), from ? $(from) : null); - - if (window.getSelection) { - window.getSelection().removeAllRanges(); - } else { - document.selection.empty(); - } - + window.getSelection().removeAllRanges(); showLineButton(); }); diff --git a/web_src/js/features/repo-common.ts b/web_src/js/features/repo-common.ts index c246d5b4b05..40932f66678 100644 --- a/web_src/js/features/repo-common.ts +++ b/web_src/js/features/repo-common.ts @@ -1,10 +1,11 @@ -import $ from 'jquery'; -import {hideElem, queryElems, showElem} from '../utils/dom.ts'; +import {queryElems} from '../utils/dom.ts'; import {POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {sleep} from '../utils.ts'; import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue'; import {createApp} from 'vue'; +import {toOriginUrl} from '../utils/url.ts'; +import {createTippy} from '../modules/tippy.ts'; async function onDownloadArchive(e) { e.preventDefault(); @@ -41,54 +42,69 @@ export function initRepoActivityTopAuthorsChart() { } } -export function initRepoCloneLink() { - const $repoCloneSsh = $('#repo-clone-ssh'); - const $repoCloneHttps = $('#repo-clone-https'); - const $inputLink = $('#repo-clone-url'); +function initCloneSchemeUrlSelection(parent: Element) { + const elCloneUrlInput = parent.querySelector('.repo-clone-url'); - if ((!$repoCloneSsh.length && !$repoCloneHttps.length) || !$inputLink.length) { - return; - } + const tabSsh = parent.querySelector('.repo-clone-ssh'); + 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', () => { - localStorage.setItem('repo-clone-protocol', 'ssh'); - window.updateCloneStates(); - }); - $repoCloneHttps.on('click', () => { - localStorage.setItem('repo-clone-protocol', 'https'); - window.updateCloneStates(); - }); + const tab = isSSH ? tabSsh : tabHttps; + if (!tab) return; + const link = toOriginUrl(tab.getAttribute('data-link')); - $inputLink.on('focus', () => { - $inputLink.trigger('select'); - }); -} - -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'); + for (const el of document.querySelectorAll('.js-clone-url')) { + if (el.nodeName === 'INPUT') { + (el as HTMLInputElement).value = link; + } else { + el.textContent = link; } - }, - message: {noResults: $dropdown[0].getAttribute('data-no-results')}, + } + for (const el of parent.querySelectorAll('.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) { diff --git a/web_src/js/features/repo-diff-commit.ts b/web_src/js/features/repo-diff-commit.ts index c115e6300ad..b270fb8568f 100644 --- a/web_src/js/features/repo-diff-commit.ts +++ b/web_src/js/features/repo-diff-commit.ts @@ -1,7 +1,7 @@ import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; import {GET} from '../modules/fetch.ts'; -async function loadBranchesAndTags(area, loadingButton) { +async function loadBranchesAndTags(area: Element, loadingButton: Element) { loadingButton.classList.add('disabled'); try { 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>) { const tagArea = area.querySelector('.tag-area'); toggleElem(tagArea.parentElement, tags.length > 0); for (const tag of tags) { @@ -23,7 +23,7 @@ function addTags(area, tags) { } } -function addBranches(area, branches, defaultBranch) { +function addBranches(area: Element, branches: Array>, defaultBranch: string) { const defaultBranchTooltip = area.getAttribute('data-text-default-branch-tooltip'); const branchArea = area.querySelector('.branch-area'); 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'); link.classList.add('muted', 'tw-px-1'); link.href = href; diff --git a/web_src/js/features/repo-graph.ts b/web_src/js/features/repo-graph.ts index 231a7266538..c9740dbedf4 100644 --- a/web_src/js/features/repo-graph.ts +++ b/web_src/js/features/repo-graph.ts @@ -22,7 +22,7 @@ export function initRepoGraphGit() { for (const link of document.querySelectorAll('.pagination a')) { const href = link.getAttribute('href'); if (!href) continue; - const url = new URL(href, window.location); + const url = new URL(href, window.location.href); const params = url.searchParams; params.set('mode', 'monochrome'); url.search = `?${params.toString()}`; @@ -38,7 +38,7 @@ export function initRepoGraphGit() { for (const link of document.querySelectorAll('.pagination a')) { const href = link.getAttribute('href'); if (!href) continue; - const url = new URL(href, window.location); + const url = new URL(href, window.location.href); const params = url.searchParams; params.delete('mode'); url.search = `?${params.toString()}`; @@ -53,7 +53,7 @@ export function initRepoGraphGit() { window.history.replaceState({}, '', window.location.pathname); } }); - const url = new URL(window.location); + const url = new URL(window.location.href); const params = url.searchParams; const updateGraph = () => { const queryString = params.toString(); @@ -103,7 +103,7 @@ export function initRepoGraphGit() { }, onAdd(toAdd) { if (toAdd === '...flow-hide-pr-refs') { - params.set('hide-pr-refs', true); + params.set('hide-pr-refs', 'true'); } else { 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')) { const flow = e.target.getAttribute('data-flow'); 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')) { const flow = e.target.getAttribute('data-flow'); if (flow === '0') return; diff --git a/web_src/js/features/repo-home.ts b/web_src/js/features/repo-home.ts index df52b87f5ae..4c69a004344 100644 --- a/web_src/js/features/repo-home.ts +++ b/web_src/js/features/repo-home.ts @@ -1,7 +1,7 @@ import {stripTags} from '../utils.ts'; import {hideElem, queryElemChildren, showElem} from '../utils/dom.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'; const {appSubUrl} = window.config; @@ -13,7 +13,7 @@ export function initRepoTopicBar() { const editDiv = document.querySelector('#topic_edit'); const viewDiv = document.querySelector('#repo-topics'); const topicDropdown = editDiv.querySelector('.ui.dropdown'); - let lastErrorToast; + let lastErrorToast: Toast; mgrBtn.addEventListener('click', () => { hideElem(viewDiv); diff --git a/web_src/js/features/repo-issue-pr-status.ts b/web_src/js/features/repo-issue-pr-status.ts index 7890b9c48db..8426b389f0e 100644 --- a/web_src/js/features/repo-issue-pr-status.ts +++ b/web_src/js/features/repo-issue-pr-status.ts @@ -1,7 +1,7 @@ export function initRepoPullRequestCommitStatus() { for (const btn of document.querySelectorAll('.commit-status-hide-checks')) { const panel = btn.closest('.commit-status-panel'); - const list = panel.querySelector('.commit-status-list'); + const list = panel.querySelector('.commit-status-list'); btn.addEventListener('click', () => { list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index e4f9ce4cde1..75410397868 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -124,7 +124,7 @@ export function initRepoIssueFilterItemLabel() { export function initRepoIssueCommentDelete() { // Delete comment - document.addEventListener('click', async (e) => { + document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { if (!e.target.matches('.delete-comment')) return; e.preventDefault(); @@ -143,7 +143,7 @@ export function initRepoIssueCommentDelete() { const counter = document.querySelector('#review-box .review-comments-counter'); let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 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); } @@ -199,7 +199,7 @@ export function initRepoIssueDependencyDelete() { export function initRepoIssueCodeCommentCancel() { // Cancel inline code comment - document.addEventListener('click', (e) => { + document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { if (!e.target.matches('.cancel-code-comment')) return; const form = e.target.closest('form'); @@ -268,12 +268,14 @@ export function initRepoPullRequestMergeInstruction() { export function initRepoPullRequestAllowMaintainerEdit() { const wrapper = document.querySelector('#allow-edits-from-maintainers'); if (!wrapper) return; - const checkbox = wrapper.querySelector('input[type="checkbox"]'); + const checkbox = wrapper.querySelector('input[type="checkbox"]'); checkbox.addEventListener('input', async () => { const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`; wrapper.classList.add('is-loading'); 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) { throw new Error('Failed to update maintainer edit permission'); } @@ -322,7 +324,7 @@ export function initRepoIssueWipTitle() { const $issueTitle = $('#issue_title'); $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'); for (const prefix of wipPrefixes) { @@ -338,7 +340,7 @@ export function initRepoIssueWipTitle() { export function initRepoIssueComments() { if (!$('.repository.view.issue .timeline').length) return; - document.addEventListener('click', (e) => { + document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { const urlTarget = document.querySelector(':target'); if (!urlTarget) return; @@ -490,7 +492,7 @@ export function initRepoPullRequestReview() { export function initRepoIssueReferenceIssue() { // Reference issue - $(document).on('click', '.reference-issue', function (event) { + $(document).on('click', '.reference-issue', function (e) { const target = this.getAttribute('data-target'); const content = document.querySelector(`#${target}`)?.textContent ?? ''; const poster = this.getAttribute('data-poster-username'); @@ -500,7 +502,7 @@ export function initRepoIssueReferenceIssue() { const textarea = modal.querySelector('textarea[name="content"]'); textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`; $(modal).modal('show'); - event.preventDefault(); + e.preventDefault(); }); } @@ -584,7 +586,7 @@ export function initRepoIssueTitleEdit() { } 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]'); if (!el) return; const pullTargetBranch = document.querySelector('#pull-target-branch'); diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts index dfea66c7ad8..eaa9e3742a8 100644 --- a/web_src/js/features/repo-legacy.ts +++ b/web_src/js/features/repo-legacy.ts @@ -8,9 +8,7 @@ import { } from './repo-issue.ts'; import {initUnicodeEscapeButton} from './repo-unicode-escape.ts'; import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; -import { - initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown, -} from './repo-common.ts'; +import {initRepoCloneButtons} from './repo-common.ts'; import {initCitationFileCopyContent} from './citation.ts'; import {initCompLabelEdit} from './comp/LabelEdit.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() { if (!$('.page-content.repository').length) return; @@ -54,7 +79,7 @@ export function initRepository() { initRepoCommonFilterSearchDropdown('.choose.branch .dropdown'); } - initRepoCloneLink(); + initRepoCloneButtons(); initCitationFileCopyContent(); initRepoSettings(); diff --git a/web_src/js/features/repo-migration.ts b/web_src/js/features/repo-migration.ts index 8f79ee14237..fb9c822f984 100644 --- a/web_src/js/features/repo-migration.ts +++ b/web_src/js/features/repo-migration.ts @@ -1,14 +1,14 @@ import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; -const service = document.querySelector('#service_type'); -const user = document.querySelector('#auth_username'); -const pass = document.querySelector('#auth_password'); -const token = document.querySelector('#auth_token'); -const mirror = document.querySelector('#mirror'); -const lfs = document.querySelector('#lfs'); -const lfsSettings = document.querySelector('#lfs_settings'); -const lfsEndpoint = document.querySelector('#lfs_endpoint'); -const items = document.querySelectorAll('#migrate_items input[type=checkbox]'); +const service = document.querySelector('#service_type'); +const user = document.querySelector('#auth_username'); +const pass = document.querySelector('#auth_password'); +const token = document.querySelector('#auth_token'); +const mirror = document.querySelector('#mirror'); +const lfs = document.querySelector('#lfs'); +const lfsSettings = document.querySelector('#lfs_settings'); +const lfsEndpoint = document.querySelector('#lfs_endpoint'); +const items = document.querySelectorAll('#migrate_items input[type=checkbox]'); export function initRepoMigration() { checkAuth(); @@ -25,11 +25,11 @@ export function initRepoMigration() { }); lfs?.addEventListener('change', setLFSSettingsVisibility); - const cloneAddr = document.querySelector('#clone_addr'); + const cloneAddr = document.querySelector('#clone_addr'); cloneAddr?.addEventListener('change', () => { - const repoName = document.querySelector('#repo_name'); + const repoName = document.querySelector('#repo_name'); 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); } -function checkItems(tokenAuth) { - let enableItems; +function checkItems(tokenAuth: boolean) { + let enableItems = false; if (tokenAuth) { enableItems = token?.value !== ''; } else { diff --git a/web_src/js/features/repo-new.ts b/web_src/js/features/repo-new.ts index 22d8c8a47b7..436288325ae 100644 --- a/web_src/js/features/repo-new.ts +++ b/web_src/js/features/repo-new.ts @@ -7,7 +7,7 @@ export function initRepoNew() { const gitignores = $('input[name="gitignores"]').val(); const license = $('input[name="license"]').val(); if (gitignores || license) { - document.querySelector('input[name="auto_init"]').checked = true; + document.querySelector('input[name="auto_init"]').checked = true; } }); } diff --git a/web_src/js/features/repo-projects.ts b/web_src/js/features/repo-projects.ts index bc2bb69a339..d1f0d17c446 100644 --- a/web_src/js/features/repo-projects.ts +++ b/web_src/js/features/repo-projects.ts @@ -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'); updateIssueCount(from); updateIssueCount(to); @@ -97,14 +97,14 @@ export function initRepoProject() { return; } - const _promise = initRepoProjectSortable(); + initRepoProjectSortable(); // no await for (const modal of document.querySelectorAll('.edit-project-column-modal')) { - const projectHeader = modal.closest('.project-column-header'); - const projectTitleLabel = projectHeader?.querySelector('.project-column-title-label'); - const projectTitleInput = modal.querySelector('.project-column-title-input'); - const projectColorInput = modal.querySelector('#new_project_column_color'); - const boardColumn = modal.closest('.project-column'); + const projectHeader = modal.closest('.project-column-header'); + const projectTitleLabel = projectHeader?.querySelector('.project-column-title-label'); + const projectTitleInput = modal.querySelector('.project-column-title-input'); + const projectColorInput = modal.querySelector('#new_project_column_color'); + const boardColumn = modal.closest('.project-column'); modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) { e.preventDefault(); try { @@ -119,7 +119,7 @@ export function initRepoProject() { } finally { projectTitleLabel.textContent = projectTitleInput?.value; projectTitleInput.closest('form')?.classList.remove('dirty'); - const dividers = boardColumn.querySelectorAll(':scope > .divider'); + const dividers = boardColumn.querySelectorAll(':scope > .divider'); if (projectColorInput.value) { const color = contrastColor(projectColorInput.value); boardColumn.style.setProperty('background', projectColorInput.value, 'important'); diff --git a/web_src/js/features/repo-release.ts b/web_src/js/features/repo-release.ts index c59ab1f3238..683c57dad1f 100644 --- a/web_src/js/features/repo-release.ts +++ b/web_src/js/features/repo-release.ts @@ -1,11 +1,11 @@ import {hideElem, showElem} from '../utils/dom.ts'; export function initRepoRelease() { - document.addEventListener('click', (e) => { + document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { if (e.target.matches('.remove-rel-attach')) { const uuid = e.target.getAttribute('data-uuid'); const id = e.target.getAttribute('data-id'); - document.querySelector(`input[name='attachment-del-${uuid}']`).value = 'true'; + document.querySelector(`input[name='attachment-del-${uuid}']`).value = 'true'; hideElem(`#attachment-${id}`); } }); @@ -28,8 +28,8 @@ function initTagNameEditor() { const newTagHelperText = el.getAttribute('data-tag-helper-new'); const existingTagHelperText = el.getAttribute('data-tag-helper-existing'); - const tagNameInput = document.querySelector('#tag-name'); - const hideTargetInput = function(tagNameInput) { + const tagNameInput = document.querySelector('#tag-name'); + const hideTargetInput = function(tagNameInput: HTMLInputElement) { const value = tagNameInput.value; const tagHelper = document.querySelector('#tag-helper'); if (existingTags.includes(value)) { @@ -42,7 +42,7 @@ function initTagNameEditor() { } }; 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); }); } diff --git a/web_src/js/features/repo-search.ts b/web_src/js/features/repo-search.ts index 185f6119d9d..9cc2dd42239 100644 --- a/web_src/js/features/repo-search.ts +++ b/web_src/js/features/repo-search.ts @@ -1,8 +1,8 @@ export function initRepositorySearch() { - const repositorySearchForm = document.querySelector('#repo-search-form'); + const repositorySearchForm = document.querySelector('#repo-search-form'); if (!repositorySearchForm) return; - repositorySearchForm.addEventListener('change', (e) => { + repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => { e.preventDefault(); const formData = new FormData(repositorySearchForm); diff --git a/web_src/js/features/repo-settings.ts b/web_src/js/features/repo-settings.ts index 9ea546f76d1..5e7edb04026 100644 --- a/web_src/js/features/repo-settings.ts +++ b/web_src/js/features/repo-settings.ts @@ -73,7 +73,7 @@ function initRepoSettingsSearchTeamBox() { function initRepoSettingsGitHook() { if (!$('.edit.githook').length) return; const filename = document.querySelector('.hook-filename').textContent; - createMonaco($('#content')[0], filename, {language: 'shell'}); + createMonaco($('#content')[0] as HTMLTextAreaElement, filename, {language: 'shell'}); } function initRepoSettingsBranches() { @@ -99,7 +99,7 @@ function initRepoSettingsBranches() { // show the `Matched` mark for the status checks that match the pattern const markMatchedStatusChecks = () => { - const patterns = (document.querySelector('#status_check_contexts').value || '').split(/[\r\n]+/); + const patterns = (document.querySelector('#status_check_contexts').value || '').split(/[\r\n]+/); const validPatterns = patterns.map((item) => item.trim()).filter(Boolean); const marks = document.querySelectorAll('.status-check-matched-mark'); @@ -122,7 +122,7 @@ function initRepoSettingsBranches() { function initRepoSettingsOptions() { if ($('.repository.settings.options').length > 0) { // 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) { $($(this).data('target')).removeClass('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'); } }); - $('.enable-system-radio').on('change', function () { + $('.enable-system-radio').on('change', function (this: HTMLInputElement) { if (this.value === 'false') { $($(this).data('target')).addClass('disabled'); if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled'); diff --git a/web_src/js/features/sshkey-helper.ts b/web_src/js/features/sshkey-helper.ts index 5531c184516..9234e3ec446 100644 --- a/web_src/js/features/sshkey-helper.ts +++ b/web_src/js/features/sshkey-helper.ts @@ -2,7 +2,7 @@ export function initSshKeyFormParser() { // Parse SSH Key document.querySelector('#ssh-key-content')?.addEventListener('input', function () { const arrays = this.value.split(' '); - const title = document.querySelector('#ssh-key-title'); + const title = document.querySelector('#ssh-key-title'); if (!title.value && arrays.length === 3 && arrays[2] !== '') { title.value = arrays[2]; } diff --git a/web_src/js/features/tablesort.ts b/web_src/js/features/tablesort.ts index 436fe0a5948..15ea358fa30 100644 --- a/web_src/js/features/tablesort.ts +++ b/web_src/js/features/tablesort.ts @@ -13,7 +13,7 @@ function tableSort(normSort, revSort, isDefault) { if (!normSort) return false; if (!revSort) revSort = ''; - const url = new URL(window.location); + const url = new URL(window.location.href); let urlSort = url.searchParams.get('sort'); if (!urlSort && isDefault) urlSort = normSort; diff --git a/web_src/js/features/tribute.ts b/web_src/js/features/tribute.ts index 193c65076ff..44588c00646 100644 --- a/web_src/js/features/tribute.ts +++ b/web_src/js/features/tribute.ts @@ -48,7 +48,7 @@ function makeCollections({mentions, emoji}) { 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 collections = makeCollections({mentions, emoji}); const tribute = new Tribute({collection: collections, noMatchTemplate: ''}); diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index 9964293727d..9780a1cf3cf 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -33,6 +33,7 @@ interface JQuery { modal: any; // fomantic tab: any; // fomantic transition: any, // fomantic + search: any, // fomantic } interface JQueryStatic { @@ -62,4 +63,5 @@ interface Window { turnstile: any, hcaptcha: any, codeEditors: any[], + updateCloneStates: () => void, } diff --git a/web_src/js/modules/toast.ts b/web_src/js/modules/toast.ts index 264ccbbdce7..36e2321743a 100644 --- a/web_src/js/modules/toast.ts +++ b/web_src/js/modules/toast.ts @@ -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 {SvgName} from '../svg.ts'; import type {Options} from 'toastify-js'; +import type StartToastifyInstance from 'toastify-js'; + +export type Toast = ReturnType; type ToastLevels = { [intent in Intent]: { @@ -38,7 +41,7 @@ type ToastOpts = { } & 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 key = `${level}-${body}`; @@ -75,14 +78,14 @@ function showToast(message: string, level: Intent, {gravity, position, duration, return toast; } -export function showInfoToast(message: string, opts?: ToastOpts) { +export function showInfoToast(message: string, opts?: ToastOpts): Toast { 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); } -export function showErrorToast(message: string, opts?: ToastOpts) { +export function showErrorToast(message: string, opts?: ToastOpts): Toast { return showToast(message, 'error', opts); } diff --git a/web_src/js/utils/url.test.ts b/web_src/js/utils/url.test.ts index 25fda79b19a..bb331a6b490 100644 --- a/web_src/js/utils/url.test.ts +++ b/web_src/js/utils/url.test.ts @@ -1,4 +1,4 @@ -import {pathEscapeSegments, isUrl} from './url.ts'; +import {pathEscapeSegments, isUrl, toOriginUrl} from './url.ts'; test('pathEscapeSegments', () => { 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('/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); +}); diff --git a/web_src/js/utils/url.ts b/web_src/js/utils/url.ts index c5a28774a9d..a7d61c5e837 100644 --- a/web_src/js/utils/url.ts +++ b/web_src/js/utils/url.ts @@ -13,3 +13,19 @@ export function isUrl(url: string): boolean { 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; +} diff --git a/web_src/js/webcomponents/origin-url.test.ts b/web_src/js/webcomponents/origin-url.test.ts deleted file mode 100644 index 19cc467d7df..00000000000 --- a/web_src/js/webcomponents/origin-url.test.ts +++ /dev/null @@ -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); -}); diff --git a/web_src/js/webcomponents/origin-url.ts b/web_src/js/webcomponents/origin-url.ts index d407fe0dff7..dbb910ce6c6 100644 --- a/web_src/js/webcomponents/origin-url.ts +++ b/web_src/js/webcomponents/origin-url.ts @@ -1,19 +1,4 @@ -// 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'. -// 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; -} +import {toOriginUrl} from '../utils/url.ts'; window.customElements.define('origin-url', class extends HTMLElement { connectedCallback() {