diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 04eb02363452d..0dd9a6687d8bb 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -16,10 +16,10 @@ parserOptions: parser: "@typescript-eslint/parser" # for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser settings: - import/extensions: [".js", ".ts"] - import/parsers: + import-x/extensions: [".js", ".ts"] + import-x/parsers: "@typescript-eslint/parser": [".js", ".ts"] - import/resolver: + import-x/resolver: typescript: true plugins: @@ -28,7 +28,7 @@ plugins: - "@typescript-eslint/eslint-plugin" - eslint-plugin-array-func - eslint-plugin-github - - eslint-plugin-i + - eslint-plugin-import-x - eslint-plugin-no-jquery - eslint-plugin-no-use-extend-native - eslint-plugin-regexp @@ -58,15 +58,15 @@ overrides: no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top] - files: ["*.config.*"] rules: - i/no-unused-modules: [0] + import-x/no-unused-modules: [0] - files: ["**/*.d.ts"] rules: - i/no-unused-modules: [0] + import-x/no-unused-modules: [0] "@typescript-eslint/consistent-type-definitions": [0] "@typescript-eslint/consistent-type-imports": [0] - files: ["web_src/js/types.ts"] rules: - i/no-unused-modules: [0] + import-x/no-unused-modules: [0] - files: ["**/*.test.*", "web_src/js/test/setup.ts"] env: vitest-globals/env: true @@ -394,49 +394,49 @@ rules: id-blacklist: [0] id-length: [0] id-match: [0] - i/consistent-type-specifier-style: [0] - i/default: [0] - i/dynamic-import-chunkname: [0] - i/export: [2] - i/exports-last: [0] - i/extensions: [2, always, {ignorePackages: true}] - i/first: [2] - i/group-exports: [0] - i/max-dependencies: [0] - i/named: [2] - i/namespace: [0] - i/newline-after-import: [0] - i/no-absolute-path: [0] - i/no-amd: [2] - i/no-anonymous-default-export: [0] - i/no-commonjs: [2] - i/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] - i/no-default-export: [0] - i/no-deprecated: [0] - i/no-dynamic-require: [0] - i/no-empty-named-blocks: [2] - i/no-extraneous-dependencies: [2] - i/no-import-module-exports: [0] - i/no-internal-modules: [0] - i/no-mutable-exports: [0] - i/no-named-as-default-member: [0] - i/no-named-as-default: [0] - i/no-named-default: [0] - i/no-named-export: [0] - i/no-namespace: [0] - i/no-nodejs-modules: [0] - i/no-relative-packages: [0] - i/no-relative-parent-imports: [0] - i/no-restricted-paths: [0] - i/no-self-import: [2] - i/no-unassigned-import: [0] - i/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}] - i/no-unused-modules: [2, {unusedExports: true}] - i/no-useless-path-segments: [2, {commonjs: true}] - i/no-webpack-loader-syntax: [2] - i/order: [0] - i/prefer-default-export: [0] - i/unambiguous: [0] + import-x/consistent-type-specifier-style: [0] + import-x/default: [0] + import-x/dynamic-import-chunkname: [0] + import-x/export: [2] + import-x/exports-last: [0] + import-x/extensions: [2, always, {ignorePackages: true}] + import-x/first: [2] + import-x/group-exports: [0] + import-x/max-dependencies: [0] + import-x/named: [2] + import-x/namespace: [0] + import-x/newline-after-import: [0] + import-x/no-absolute-path: [0] + import-x/no-amd: [2] + import-x/no-anonymous-default-export: [0] + import-x/no-commonjs: [2] + import-x/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] + import-x/no-default-export: [0] + import-x/no-deprecated: [0] + import-x/no-dynamic-require: [0] + import-x/no-empty-named-blocks: [2] + import-x/no-extraneous-dependencies: [2] + import-x/no-import-module-exports: [0] + import-x/no-internal-modules: [0] + import-x/no-mutable-exports: [0] + import-x/no-named-as-default-member: [0] + import-x/no-named-as-default: [0] + import-x/no-named-default: [0] + import-x/no-named-export: [0] + import-x/no-namespace: [0] + import-x/no-nodejs-modules: [0] + import-x/no-relative-packages: [0] + import-x/no-relative-parent-imports: [0] + import-x/no-restricted-paths: [0] + import-x/no-self-import: [2] + import-x/no-unassigned-import: [0] + import-x/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}] + import-x/no-unused-modules: [2, {unusedExports: true}] + import-x/no-useless-path-segments: [2, {commonjs: true}] + import-x/no-webpack-loader-syntax: [2] + import-x/order: [0] + import-x/prefer-default-export: [0] + import-x/unambiguous: [0] init-declarations: [0] line-comment-position: [0] logical-assignment-operators: [0] diff --git a/go.mod b/go.mod index 80b62ce83f867..17be4cbd526d5 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 d1b7890fb68cc..73bdb44e33767 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 7ef370e89c5d2..bf51bd6c14857 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 98b097f8713d3..42834f6e8863b 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 1a0f241e61590..4f6ad96d222d7 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 6f705150097f6..4c293f3f2a9c8 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 7c3ba75bb072f..06a6a46c234fc 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 a43c6be005975..09dcbf4804c88 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 9332319339215..1066e96272575 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 e752ae6f2436c..deb19adc49dd9 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/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index f0abce0d4bfa8..776d2bdc2bf2c 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -47,7 +47,7 @@ webauthn_error_unknown=Ocorreu um erro desconhecido. Tente novamente, por favor. webauthn_error_insecure=`WebAuthn apenas suporta conexões seguras. Para testar sobre HTTP, pode usar a origem "localhost" ou "127.0.0.1"` webauthn_error_unable_to_process=O servidor não conseguiu processar o seu pedido. webauthn_error_duplicated=A chave de segurança não é permitida neste pedido. Certifique-se de que a chave não está já registada. -webauthn_error_empty=Você tem que definir um nome para esta chave. +webauthn_error_empty=Tem de definir um nome para esta chave. webauthn_error_timeout=O tempo limite foi atingido antes que a sua chave pudesse ser lida. Recarregue esta página e tente novamente. webauthn_reload=Recarregar @@ -1109,6 +1109,7 @@ delete_preexisting_success=Eliminados os ficheiros não adoptados em %s blame_prior=Ver a responsabilização anterior a esta modificação blame.ignore_revs=Ignorando as revisões em .git-blame-ignore-revs. Clique aqui para contornar e ver a vista normal de responsabilização. blame.ignore_revs.failed=Falhou ao ignorar as revisões em .git-blame-ignore-revs. +user_search_tooltip=Mostra um máximo de 30 utilizadores tree_path_not_found_commit=A localização %[1]s não existe no cometimento %[2]s tree_path_not_found_branch=A localização %[1]s não existe no ramo %[2]s @@ -1527,6 +1528,8 @@ issues.filter_assignee=Encarregado issues.filter_assginee_no_select=Todos os encarregados issues.filter_assginee_no_assignee=Sem encarregado issues.filter_poster=Autor(a) +issues.filter_user_placeholder=Procurar utilizadores +issues.filter_user_no_select=Todos os utilizadores issues.filter_type=Tipo issues.filter_type.all_issues=Todas as questões issues.filter_type.assigned_to_you=Atribuídas a si diff --git a/package-lock.json b/package-lock.json index e3f7a0116f763..53bd5bc4f1f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,7 +87,7 @@ "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.1.3", - "eslint-plugin-i": "2.29.1", + "eslint-plugin-import-x": "4.5.0", "eslint-plugin-no-jquery": "3.1.0", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.1.0", @@ -8385,56 +8385,6 @@ "node": "*" } }, - "node_modules/eslint-plugin-i": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-i/-/eslint-plugin-i-2.29.1.tgz", - "integrity": "sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "doctrine": "^3.0.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "get-tsconfig": "^4.7.2", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://opencollective.com/unts" - }, - "peerDependencies": { - "eslint": "^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-i/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-i/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-i18n-text": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-i18n-text/-/eslint-plugin-i18n-text-1.0.1.tgz", @@ -8479,6 +8429,48 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, + "node_modules/eslint-plugin-import-x": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.5.0.tgz", + "integrity": "sha512-l0OTfnPF8RwmSXfjT75N8d6ZYLVrVYWpaGlgvVkVqFERCI5SyBfDP7QEMr3kt0zWi2sOa9EQ47clbdFsHkF83Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "^8.1.0", + "@typescript-eslint/utils": "^8.1.0", + "debug": "^4.3.4", + "doctrine": "^3.0.0", + "eslint-import-resolver-node": "^0.3.9", + "get-tsconfig": "^4.7.3", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3", + "semver": "^7.6.3", + "stable-hash": "^0.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-import-x/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index d30aedc54f59e..3a81e64822833 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.1.3", - "eslint-plugin-i": "2.29.1", + "eslint-plugin-import-x": "4.5.0", "eslint-plugin-no-jquery": "3.1.0", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.1.0", diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 4e194f65fa1b4..47ea7137b8250 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 e30129bb44ccd..717d7cbce1dc3 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 e0539f53b0c5d..b318c4a621a60 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 91952c8a06db2..03b7a561daac6 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 0000000000000..8cbeda132dd8b --- /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 40dae76dc7164..0000000000000 --- 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 d3a81bc51d201..7170fe360203d 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 46d0398c21031..4e6d375b51710 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 9d718d7197a41..34a5df8f7762e 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 3edfbb347456f..ea61c3736ae5f 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 045cc41d81e7d..ca8954928d26f 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 c8e0b4254ca8e..2bb0a4f006888 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 d307e87d4e0f8..667ba0908ce20 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 b967ccad1ec69..7889dfaf3b797 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/tools/generate-images.js b/tools/generate-images.js index 0bd3af29e4c3d..d28e0916f7351 100755 --- a/tools/generate-images.js +++ b/tools/generate-images.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -import imageminZopfli from 'imagemin-zopfli'; // eslint-disable-line i/no-unresolved -import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node'; // eslint-disable-line i/no-unresolved +import imageminZopfli from 'imagemin-zopfli'; // eslint-disable-line import-x/no-unresolved +import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node'; // eslint-disable-line import-x/no-unresolved import {optimize} from 'svgo'; import {readFile, writeFile} from 'node:fs/promises'; import {argv, exit} from 'node:process'; diff --git a/web_src/css/index.css b/web_src/css/index.css index 158ae42d3e325..02513aebc1cd1 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 f5785c41a7773..9a43e10e826b7 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 0000000000000..15709a78f652e --- /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 0000000000000..e7570304194d3 --- /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 0000000000000..eab2124d6f489 --- /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 ba502d32161d5..ca59dadb9c806 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-common.ts b/web_src/js/features/repo-common.ts index 5185a7ca43510..90860720e4fc2 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); + } + + const tab = isSSH ? tabSsh : tabHttps; + if (!tab) return; + const link = toOriginUrl(tab.getAttribute('data-link')); + + for (const el of document.querySelectorAll('.js-clone-url')) { + if (el.nodeName === 'INPUT') { + (el as HTMLInputElement).value = link; + } else { + el.textContent = link; + } + } + for (const el of parent.querySelectorAll('.js-clone-url-editor')) { + el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link)); + } + }; - $repoCloneSsh.on('click', () => { + updateClonePanelUi(); + // tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server + tabSsh?.addEventListener('click', () => { localStorage.setItem('repo-clone-protocol', 'ssh'); - window.updateCloneStates(); + updateClonePanelUi(); }); - $repoCloneHttps.on('click', () => { + tabHttps?.addEventListener('click', () => { localStorage.setItem('repo-clone-protocol', 'https'); - window.updateCloneStates(); + updateClonePanelUi(); }); - - $inputLink.on('focus', () => { - $inputLink.trigger('select'); + elCloneUrlInput.addEventListener('focus', () => { + elCloneUrlInput.select(); }); } -export 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 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 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 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-legacy.ts b/web_src/js/features/repo-legacy.ts index dfea66c7ad811..eaa9e3742a869 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/utils/url.test.ts b/web_src/js/utils/url.test.ts index 25fda79b19af9..bb331a6b4901a 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 c5a28774a9d42..a7d61c5e837b7 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 19cc467d7dfc7..0000000000000 --- 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 d407fe0dff7d1..dbb910ce6c694 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() {