Merge branch 'forgejo' into landingpagedetails
Some checks failed
Integration tests for the release process / release-simulation (push) Has been cancelled

This commit is contained in:
David Rotermund 2025-02-18 10:40:49 +00:00
commit 20c03184be
44 changed files with 4415 additions and 1056 deletions

View file

@ -28,7 +28,7 @@ jobs:
runs-on: docker runs-on: docker
container: container:
image: data.forgejo.org/renovate/renovate:39.164.1 image: data.forgejo.org/renovate/renovate:39.171.2
steps: steps:
- name: Load renovate repo cache - name: Load renovate repo cache

View file

@ -49,7 +49,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.29.0 # renovate: datasource=go DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.29.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1 # renovate: datasource=go GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@39.164.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate RENOVATE_NPM_PACKAGE ?= renovate@39.171.2 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
@ -127,6 +127,11 @@ FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
show-version-api: show-version-api:
@echo ${FORGEJO_VERSION_API} @echo ${FORGEJO_VERSION_API}
# Strip binaries by default to reduce size, allow overriding for debugging
STRIP ?= 1
ifeq ($(STRIP),1)
LDFLAGS := $(LDFLAGS) -s -w
endif
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)" LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
@ -827,7 +832,7 @@ check: test
.PHONY: install $(TAGS_PREREQ) .PHONY: install $(TAGS_PREREQ)
install: $(wildcard *.go) install: $(wildcard *.go)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)'
.PHONY: build .PHONY: build
build: frontend backend build: frontend backend
@ -855,13 +860,13 @@ merge-locales:
@echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY" @echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY"
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@
forgejo: $(EXECUTABLE) forgejo: $(EXECUTABLE)
ln -f $(EXECUTABLE) forgejo ln -f $(EXECUTABLE) forgejo
static-executable: $(GO_SOURCES) $(TAGS_PREREQ) static-executable: $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE) CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE)
.PHONY: release .PHONY: release
release: frontend generate release-linux release-copy release-compress vendor release-sources release-check release: frontend generate release-linux release-copy release-compress vendor release-sources release-check

View file

@ -219,8 +219,13 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
return "", 0, fmt.Errorf("ParsePublicKey: %w", err) return "", 0, fmt.Errorf("ParsePublicKey: %w", err)
} }
pkeyType := pkey.Type()
if certPkey, ok := pkey.(*ssh.Certificate); ok {
pkeyType = certPkey.Key.Type()
}
// The ssh library can parse the key, so next we find out what key exactly we have. // The ssh library can parse the key, so next we find out what key exactly we have.
switch pkey.Type() { switch pkeyType {
case ssh.KeyAlgoDSA: case ssh.KeyAlgoDSA:
rawPub := struct { rawPub := struct {
Name string Name string

View file

@ -35,6 +35,7 @@ func Test_SSHParsePublicKey(t *testing.T) {
{"ecdsa-384", false, "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"}, {"ecdsa-384", false, "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
{"ecdsa-sk", true, "ecdsa-sk", 256, "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"}, {"ecdsa-sk", true, "ecdsa-sk", 256, "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
{"ed25519-sk", true, "ed25519-sk", 256, "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"}, {"ed25519-sk", true, "ed25519-sk", 256, "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
{"ed25519-cert-v01", true, "ed25519", 256, "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIAlIAPlEj0mYQzQo8Ks0Nm/Ct8ceNkyJSf4DLuF5l7+5AAAAIEuWAoaBo2tT29/oMNnoDfdAPRCIdM2RGapKUhY4nDfLRgPQwfnRoc0AAAABAAAAcHZhdWx0LW9pZGMtNmRhYjdiZDgtNDg5YS00MDFkLTg3ZmItNjdjNTlhMDZkZDkxLTNjNTk2M2YyMGRmMDM3MDkyMzc1YmNiYmNiNzkxY2EyZWIxM2I0NGZhMzc2NTcwMWI0MjMwODU0MWFmNjhkNTgAAAALAAAAB2Zvcmdlam8AAAAAZ6/RUQAAAABn115vAAAAAAAAAAAAAAAAAAACFwAAAAdzc2gtcnNhAAAAAwEAAQAAAgEAySnM/TvD117GyKgOgMatDB2t+fCHORFaWVmH5SaadAzNJ2DfDAauRSLfnim1xdgAOMTzsPEEHH47zyYMjE85o2AiJxrfUBMw3O/7AbNc6+HyLr/txH4+vD9tWQknKnpVWM+3Z9wiHDcOdKRoXCmFZKJH1vxs16GNWjwbrfNiimv7Oi0fadgvTDKX603gpLTuVDXqs9eQFLCONptei86JYBAJqaHvg51k8YUCKt9WFqKAj7BJUWmrDvhv5VFMOsnZieJjqxkoxnpsQNlXfPzxK0vIpJofbYfWwscv/g9WZypHwO1ZR2PqzKm99YrSdr8w5256l0f44vsF0NSP0N7bDQEfYYnRGj8zWTYCBFD+uYF7AxIeaRUpZoTQO8MvCHOLMIDinNgEeCUvNA2v9zHl4BGq+PQjzUKAgJiKj0MZeiCDAmQ22g83ggQlB6BOrBb1fNa/S1cmTbGHQ2oAN358aqkmHVCBhPOyA2Rf65D2M2vzDlUdOsNDUIWAHk7GbwSNGDgcYfTWqtR5fTzp2MJovMh1dDUDXjOvojbhzjJtSy9+rzUYIv18aXdOitzVBgPMWdeVCZFZv4OKF+5MiqxQvedUvfiSjsdxZWLxyT1CJ88G3MzxNMS/Djm86T8h/Oa55bdvFtqpsLfvpIqq0pnXq1V/vF2j1MWwRB5z5Xh/HtEAAAIUAAAADHJzYS1zaGEyLTI1NgAAAgB2I2gzqemQl8/ETxtakALlm/2BpUcbhADcFWuoH6BCPnWHuTSwf3OayM6KXv1PQfL3YFRoi9Afrp8kVFL6DePsmKH+0BUEMz71sZ7v1ty7pwfzibItGnpTbQXhzbEiNYAFoz77rl7oaXF7pV6JNZhj3DVAB5gVA2oN5KRNVxijz+6uyuFJEw1HIl1C7GworvGwZcN7BThTEh3i72/Vntejy9Z8uGVjSFjS0rjRo2oXK1LKN0rVt66p3TmCWHouLkVnOTk0qrhLGlL2HVyo24OYHbkAAObD9b6aMDYlmluk6NsaiTKsSTsvMrbIbjtFQlh7nNyoPhZ0VMwaT1l10pDQ5uxWWZjKGIkz4xM1ZfpBszjJNPo+ivYQnTSjj9LwkbLAT9a/5LawSj80TGcLEMO+0eyPdJsP0wYmOVRFAZeRiBgwb3HrzcF6Wqr8icj1EjYkKSy9YFHGTnFBGknpdh3HGwghRXrCUwAnSM76db9pv4/qowT8LthtJ3dY5Epe0OJ1Tqm+q8bkGH4gB+7uqLSqM5pIHSKLp7lfHQBt1J6xa7H2saiweaWjU+QGTgQ2Lg+uUC5DXJrmm60CeFJ4BoGhUenDlgijbQpjH/l6330PbwefgjWtUK/pqaEA4lCoPyvJ+eF2DbYfPiAIBAFQnhVJJae4AH+XoCt29nb2j30ztg== nocomment"},
} }
for _, tc := range testCases { for _, tc := range testCases {

View file

@ -45,6 +45,7 @@
full_name: ' < U<se>r Tw<o > >< ' full_name: ' < U<se>r Tw<o > >< '
email: user2@example.com email: user2@example.com
keep_email_private: true keep_email_private: true
keep_pronouns_private: true
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: ZogKvWdyEx:password passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy passwd_hash_algo: dummy
@ -350,6 +351,7 @@
full_name: User Ten full_name: User Ten
email: user10@example.com email: user10@example.com
keep_email_private: false keep_email_private: false
keep_pronouns_private: true
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: ZogKvWdyEx:password passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy passwd_hash_algo: dummy

View file

@ -58,40 +58,42 @@ var migrations = []*Migration{
NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting), NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting),
// v9 -> v10 // v9 -> v10
NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser), NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser),
// v11 -> v12 // v10 -> v11
NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue), NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue),
// v12 -> v13 // v11 -> v12
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount), NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount),
// v13 -> v14 // v12 -> v13
NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease), NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease),
// v14 -> v15 // v13 -> v14
NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge), NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge),
// v15 -> v16 // v14 -> v15
NewMigration("Create the `federation_host` table", CreateFederationHostTable), NewMigration("Create the `federation_host` table", CreateFederationHostTable),
// v16 -> v17 // v15 -> v16
NewMigration("Create the `federated_user` table", CreateFederatedUserTable), NewMigration("Create the `federated_user` table", CreateFederatedUserTable),
// v17 -> v18 // v16 -> v17
NewMigration("Add `normalized_federated_uri` column to `user` table", AddNormalizedFederatedURIToUser), NewMigration("Add `normalized_federated_uri` column to `user` table", AddNormalizedFederatedURIToUser),
// v18 -> v19 // v17 -> v18
NewMigration("Create the `following_repo` table", CreateFollowingRepoTable), NewMigration("Create the `following_repo` table", CreateFollowingRepoTable),
// v19 -> v20 // v18 -> v19
NewMigration("Add external_url to attachment table", AddExternalURLColumnToAttachmentTable), NewMigration("Add external_url to attachment table", AddExternalURLColumnToAttachmentTable),
// v20 -> v21 // v19 -> v20
NewMigration("Creating Quota-related tables", CreateQuotaTables), NewMigration("Creating Quota-related tables", CreateQuotaTables),
// v21 -> v22 // v20 -> v21
NewMigration("Add SSH keypair to `pull_mirror` table", AddSSHKeypairToPushMirror), NewMigration("Add SSH keypair to `pull_mirror` table", AddSSHKeypairToPushMirror),
// v22 -> v23 // v21 -> v22
NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential), NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential),
// v23 -> v24 // v22 -> v23
NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge), NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge),
// v24 -> v25 // v23 -> v24
NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken), NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken),
// v25 -> v26 // v24 -> v25
NewMigration("Migrate `secret` column to store keying material", MigrateTwoFactorToKeying), NewMigration("Migrate `secret` column to store keying material", MigrateTwoFactorToKeying),
// v26 -> v27 // v25 -> v26
NewMigration("Add `hash_blake2b` column to `package_blob` table", AddHashBlake2bToPackageBlob), NewMigration("Add `hash_blake2b` column to `package_blob` table", AddHashBlake2bToPackageBlob),
// v27 -> v28 // v26 -> v27
NewMigration("Add `created_unix` column to `user_redirect` table", AddCreatedUnixToRedirect), NewMigration("Add `created_unix` column to `user_redirect` table", AddCreatedUnixToRedirect),
// v27 -> v28
NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser),
} }
// GetCurrentDBVersion returns the current Forgejo database version. // GetCurrentDBVersion returns the current Forgejo database version.

View file

@ -0,0 +1,15 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations //nolint:revive
import "xorm.io/xorm"
func AddHidePronounsOptionToUser(x *xorm.Engine) error {
type User struct {
ID int64 `xorm:"pk autoincr"`
KeepPronounsPrivate bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(&User{})
}

View file

@ -154,6 +154,7 @@ type User struct {
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
Theme string `xorm:"NOT NULL DEFAULT ''"` Theme string `xorm:"NOT NULL DEFAULT ''"`
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"` KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
KeepPronounsPrivate bool `xorm:"NOT NULL DEFAULT false"`
EnableRepoUnitHints bool `xorm:"NOT NULL DEFAULT true"` EnableRepoUnitHints bool `xorm:"NOT NULL DEFAULT true"`
} }
@ -500,6 +501,16 @@ func (u *User) GetCompleteName() string {
return u.Name return u.Name
} }
// GetPronouns returns an empty string, if the user has set to keep his
// pronouns private from non-logged in users, otherwise the pronouns
// are returned.
func (u *User) GetPronouns(signed bool) string {
if u.KeepPronounsPrivate && !signed {
return ""
}
return u.Pronouns
}
func gitSafeName(name string) string { func gitSafeName(name string) string {
return strings.TrimSpace(strings.NewReplacer("\n", "", "<", "", ">", "").Replace(name)) return strings.TrimSpace(strings.NewReplacer("\n", "", "<", "", ">", "").Replace(name))
} }
@ -854,48 +865,46 @@ func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
// VerifyUserActiveCode verifies that the code is valid for the given purpose for this user. // VerifyUserActiveCode verifies that the code is valid for the given purpose for this user.
// If delete is specified, the token will be deleted. // If delete is specified, the token will be deleted.
func VerifyUserAuthorizationToken(ctx context.Context, code string, purpose auth.AuthorizationPurpose, delete bool) (*User, error) { func VerifyUserAuthorizationToken(ctx context.Context, code string, purpose auth.AuthorizationPurpose) (user *User, deleteToken func() error, err error) {
lookupKey, validator, found := strings.Cut(code, ":") lookupKey, validator, found := strings.Cut(code, ":")
if !found { if !found {
return nil, nil return nil, nil, nil
} }
authToken, err := auth.FindAuthToken(ctx, lookupKey, purpose) authToken, err := auth.FindAuthToken(ctx, lookupKey, purpose)
if err != nil { if err != nil {
if errors.Is(err, util.ErrNotExist) { if errors.Is(err, util.ErrNotExist) {
return nil, nil return nil, nil, nil
} }
return nil, err return nil, nil, err
} }
if authToken.IsExpired() { if authToken.IsExpired() {
return nil, auth.DeleteAuthToken(ctx, authToken) return nil, nil, auth.DeleteAuthToken(ctx, authToken)
} }
rawValidator, err := hex.DecodeString(validator) rawValidator, err := hex.DecodeString(validator)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 { if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 {
return nil, errors.New("validator doesn't match") return nil, nil, errors.New("validator doesn't match")
} }
u, err := GetUserByID(ctx, authToken.UID) u, err := GetUserByID(ctx, authToken.UID)
if err != nil { if err != nil {
if IsErrUserNotExist(err) { if IsErrUserNotExist(err) {
return nil, nil return nil, nil, nil
} }
return nil, err return nil, nil, err
} }
if delete { deleteToken = func() error {
if err := auth.DeleteAuthToken(ctx, authToken); err != nil { return auth.DeleteAuthToken(ctx, authToken)
return nil, err
}
} }
return u, nil return u, deleteToken, nil
} }
// ValidateUser check if user is valid to insert / update into database // ValidateUser check if user is valid to insert / update into database

View file

@ -756,13 +756,13 @@ func TestVerifyUserAuthorizationToken(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
t.Run("Wrong purpose", func(t *testing.T) { t.Run("Wrong purpose", func(t *testing.T) {
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.PasswordReset, false) u, _, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.PasswordReset)
require.NoError(t, err) require.NoError(t, err)
assert.Nil(t, u) assert.Nil(t, u)
}) })
t.Run("No delete", func(t *testing.T) { t.Run("No delete", func(t *testing.T) {
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, false) u, _, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation)
require.NoError(t, err) require.NoError(t, err)
assert.EqualValues(t, user.ID, u.ID) assert.EqualValues(t, user.ID, u.ID)
@ -772,9 +772,10 @@ func TestVerifyUserAuthorizationToken(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, true) u, deleteToken, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation)
require.NoError(t, err) require.NoError(t, err)
assert.EqualValues(t, user.ID, u.ID) assert.EqualValues(t, user.ID, u.ID)
require.NoError(t, deleteToken())
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation) authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
require.ErrorIs(t, err, util.ErrNotExist) require.ErrorIs(t, err, util.ErrNotExist)
@ -795,3 +796,42 @@ func TestGetInactiveUsers(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, users) require.Empty(t, users)
} }
func TestPronounsPrivacy(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
t.Run("EmptyPronounsIfNoneSet", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user.Pronouns = ""
user.KeepPronounsPrivate = false
assert.Equal(t, "", user.GetPronouns(false))
})
t.Run("EmptyPronounsIfSetButPrivateAndNotLoggedIn", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user.Pronouns = "any"
user.KeepPronounsPrivate = true
assert.Equal(t, "", user.GetPronouns(false))
})
t.Run("ReturnPronounsIfSetAndNotPrivateAndNotLoggedIn", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user.Pronouns = "any"
user.KeepPronounsPrivate = false
assert.Equal(t, "any", user.GetPronouns(false))
})
t.Run("ReturnPronounsIfSetAndPrivateAndLoggedIn", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user.Pronouns = "any"
user.KeepPronounsPrivate = false
assert.Equal(t, "any", user.GetPronouns(true))
})
t.Run("ReturnPronounsIfSetAndNotPrivateAndLoggedIn", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user.Pronouns = "any"
user.KeepPronounsPrivate = true
assert.Equal(t, "any", user.GetPronouns(true))
})
}

View file

@ -1,4 +1,5 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package repository package repository
@ -107,6 +108,9 @@ func getLicensePlaceholder(name string) *licensePlaceholder {
} }
// Other special placeholders can be added here. // Other special placeholders can be added here.
} else if name == "BSD-4-Clause" {
ret.Owner = append(ret.Owner, "COPYRIGHT HOLDER")
ret.Owner = append(ret.Owner, "the organization")
} }
return ret return ret
} }

View file

@ -1,4 +1,5 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package repository package repository
@ -170,6 +171,31 @@ Copyright (C) 2023 by Gitea teabot@gitea.io
... ...
... THE AUTHOR BE LIABLE FOR ... ... THE AUTHOR BE LIABLE FOR ...
`,
},
{
name: "BSD-4-Clause",
args: args{
name: "BSD-4-Clause",
values: &LicenseValues{Year: "2025", Owner: "Forgejo", Email: "hello@forgejo.org", Repo: "forgejo"},
origin: `
Copyright (c) <year> <owner>. All rights reserved.
... includes software developed by the organization.
... Neither the name of the copyright holder nor
... PROVIDED BY COPYRIGHT HOLDER "AS IS" ... NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE ...
`,
},
want: `
Copyright (c) 2025 Forgejo. All rights reserved.
... includes software developed by Forgejo.
... Neither the name of the copyright holder nor
... PROVIDED BY Forgejo "AS IS" ... NO EVENT SHALL Forgejo BE LIABLE ...
`, `,
}, },
} }

View file

@ -84,6 +84,7 @@ type UserSettings struct {
EnableRepoUnitHints bool `json:"enable_repo_unit_hints"` EnableRepoUnitHints bool `json:"enable_repo_unit_hints"`
// Privacy // Privacy
HideEmail bool `json:"hide_email"` HideEmail bool `json:"hide_email"`
HidePronouns bool `json:"hide_pronouns"`
HideActivity bool `json:"hide_activity"` HideActivity bool `json:"hide_activity"`
} }
@ -101,6 +102,7 @@ type UserSettingsOptions struct {
EnableRepoUnitHints *bool `json:"enable_repo_unit_hints"` EnableRepoUnitHints *bool `json:"enable_repo_unit_hints"`
// Privacy // Privacy
HideEmail *bool `json:"hide_email"` HideEmail *bool `json:"hide_email"`
HidePronouns *bool `json:"hide_pronouns"`
HideActivity *bool `json:"hide_activity"` HideActivity *bool `json:"hide_activity"`
} }

View file

@ -853,6 +853,8 @@ email_preference_set_success = Email preference has been set successfully.
add_openid_success = The new OpenID address has been added. add_openid_success = The new OpenID address has been added.
keep_email_private = Hide email address keep_email_private = Hide email address
keep_email_private_popup = Your email address will not be shown on your profile and will not be the default for commits made via the web interface, like file uploads, edits, and merge commits. Instead, a special address %s can be used to link commits to your account. This option will not affect existing commits. keep_email_private_popup = Your email address will not be shown on your profile and will not be the default for commits made via the web interface, like file uploads, edits, and merge commits. Instead, a special address %s can be used to link commits to your account. This option will not affect existing commits.
keep_pronouns_private = Only show pronouns to authenticated users
keep_pronouns_private.description = This will hide your pronouns from visitors that are not logged in.
openid_desc = OpenID lets you delegate authentication to an external provider. openid_desc = OpenID lets you delegate authentication to an external provider.
manage_ssh_keys = Manage SSH keys manage_ssh_keys = Manage SSH keys
@ -3935,4 +3937,4 @@ filepreview.lines = Lines %[1]d to %[2]d in %[3]s
filepreview.truncated = Preview has been truncated filepreview.truncated = Preview has been truncated
[translation_meta] [translation_meta]
test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :) test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :)

View file

@ -134,6 +134,11 @@ new_repo.title = مخزن جدید
new_migrate.title = مهاجرت جدید new_migrate.title = مهاجرت جدید
new_repo.link = مخزن جدید new_repo.link = مخزن جدید
filter = فیلتر
filter.is_archived = بایگانی شده
filter.public = عمومی
filter.private = خصوصی
[aria] [aria]
[heatmap] [heatmap]
@ -1987,6 +1992,8 @@ error.csv.too_large=نمی توان این فایل را رندر کرد زیر
error.csv.unexpected=نمی توان این فایل را رندر کرد زیرا حاوی یک کاراکتر غیرمنتظره در خط %d و ستون %d است. error.csv.unexpected=نمی توان این فایل را رندر کرد زیرا حاوی یک کاراکتر غیرمنتظره در خط %d و ستون %d است.
error.csv.invalid_field_count=نمی توان این فایل را رندر کرد زیرا تعداد فیلدهای آن در خط %d اشتباه است. error.csv.invalid_field_count=نمی توان این فایل را رندر کرد زیرا تعداد فیلدهای آن در خط %d اشتباه است.
milestones.filter_sort.name = نام
[graphs] [graphs]
[org] [org]
@ -2540,6 +2547,9 @@ notices.op=عملیات.
notices.delete_success=گزارش سیستم حذف شده است. notices.delete_success=گزارش سیستم حذف شده است.
config_summary = چکیده
config_settings = تنظيمات
[action] [action]
create_repo=مخزن ایجاد شده <a href="%s"> %s</a> create_repo=مخزن ایجاد شده <a href="%s"> %s</a>
rename_repo=مخزن تغییر نام داد از <code>%[1]s</code> به <a href="%[2]s">%[3]s</a> rename_repo=مخزن تغییر نام داد از <code>%[1]s</code> به <a href="%[2]s">%[3]s</a>
@ -2669,3 +2679,5 @@ executable_file = فایل اجرایی
normal_file = فایل معمولی normal_file = فایل معمولی
changed_filemode = %[1] ها ← %[2] ها changed_filemode = %[1] ها ← %[2] ها
directory = پوشه directory = پوشه
[search]

File diff suppressed because it is too large Load diff

View file

@ -1270,6 +1270,8 @@ topic.done=Kész
milestones.filter_sort.name = Név
[graphs] [graphs]
[org] [org]
@ -1684,6 +1686,9 @@ config.cache_item_ttl = Gyorsítótárelem TTL értéke
config.app_data_path = Alkalmazásadatok elérési útja config.app_data_path = Alkalmazásadatok elérési útja
config_summary = Összefoglaló
config_settings = Beállítások
[action] [action]
create_repo=létrehozott tárolót: <a href="%s"> %s</a> create_repo=létrehozott tárolót: <a href="%s"> %s</a>
rename_repo=átnevezte a(z) <code>%[1]s</code> tárolót <a href="%[2]s">%[3]s</a>-ra/re rename_repo=átnevezte a(z) <code>%[1]s</code> tárolót <a href="%[2]s">%[3]s</a>-ra/re

View file

@ -84,15 +84,93 @@ concept_code_repository=Repositori
name=Nama name=Nama
re_type = Konfirmasi Kata Sandi
webauthn_insert_key = Masukkan kunci keamanan anda
webauthn_sign_in = Tekan tombol pada kunci keamanan Anda. Jika kunci keamanan Anda tidak memiliki tombol, masukkan kembali.
webauthn_press_button = Silakan tekan tombol pada kunci keamanan Anda…
webauthn_use_twofa = Gunakan kode dua faktor dari telepon Anda
webauthn_error = Tidak dapat membaca kunci keamanan Anda.
webauthn_unsupported_browser = Browser Anda saat ini tidak mendukung WebAuthn.
webauthn_error_unknown = Terdapat kesalahan yang tidak diketahui. Mohon coba lagi.
webauthn_error_insecure = `WebAuthn hanya mendukung koneksi aman. Untuk pengujian melalui HTTP, Anda dapat menggunakan "localhost" atau "127.0.0.1"`
webauthn_error_unable_to_process = Server tidak dapat memproses permintaan Anda.
webauthn_error_duplicated = Kunci keamanan tidak diperbolehkan untuk permintaan ini. Pastikan bahwa kunci ini belum terdaftar sebelumnya.
webauthn_error_empty = Anda harus menetapkan nama untuk kunci ini.
webauthn_error_timeout = Waktu habis sebelum kunci Anda dapat dibaca. Mohon muat ulang halaman ini dan coba lagi.
new_project = Proyek Baru
new_project_column = Kolom Baru
ok = Oke
retry = Coba lagi
rerun = Jalankan ulang
rerun_all = Jalankan ulang semua job
remove_label_str = `Hapus item "%s"`
view = Tampilan
test = Pengujian
locked = Terkunci
copy = Salin
copy_url = Salin URL
copy_hash = Salin hash
copy_content = Salin konten
copy_branch = Salin nama branch
copy_success = Tersalin!
copy_error = Gagal menyalin
copy_type_unsupported = Tipe berkas ini tidak dapat disalin
error = Gangguan
error404 = Halaman yang akan kamu akses <strong>tidak dapat ditemukan</strong> atau <strong>kamu tidak memiliki akses </strong> untuk melihatnya.
go_back = Kembali
invalid_data = Data invalid: %v
never = Tidak Pernah
unknown = Tidak diketahui
rss_feed = Umpan Berita
pin = Sematkan
unpin = Lepas sematan
artifacts = Artefak
archived = Diarsipkan
concept_system_global = Global
concept_user_individual = Perorangan
show_full_screen = Tampilkan layar penuh
download_logs = Unduh Logs
confirm_delete_selected = Konfirmasi untuk menghapus semua item yang dipilih?
value = Nilai
filter = Saring
filter.is_archived = Diarsipkan
filter.not_archived = Tidak Diarsipkan
filter.public = Publik
filter.private = Pribadi
[aria] [aria]
navbar = Bar Navigasi
footer = Footer
footer.links = Tautan
[heatmap] [heatmap]
number_of_contributions_in_the_last_12_months = %s Kontribusi pada 12 bulan terakhir
less = Lebih sedikit
more = Lebih banyak
[editor] [editor]
buttons.heading.tooltip = Tambahkan heading
buttons.bold.tooltip = Tambahkan teks Tebal
buttons.italic.tooltip = Tambahkan teks Miring
buttons.quote.tooltip = Kutip teks
buttons.code.tooltip = Tambah Kode
buttons.link.tooltip = Tambahkan tautan
buttons.list.unordered.tooltip = Tambah daftar titik
buttons.list.ordered.tooltip = Tambah daftar angka
buttons.list.task.tooltip = Tambahkan daftar tugas
buttons.mention.tooltip = Tandai pengguna atau tim
buttons.ref.tooltip = Merujuk pada isu atau permintaan tarik
buttons.switch_to_legacy.tooltip = Gunakan editor versi lama
buttons.enable_monospace_font = Aktifkan font monospace
buttons.disable_monospace_font = Non-Aktifkan font monospace
[filter] [filter]
string.asc = A - Z
string.desc = Z - A
[error] [error]
occurred = Terjadi kesalahan
not_found = Target tidak dapat ditemukan.
[startpage] [startpage]
app_desc=Sebuah layanan hosting Git sendiri yang tanpa kesulitan app_desc=Sebuah layanan hosting Git sendiri yang tanpa kesulitan
@ -124,6 +202,9 @@ require_sign_in_view=Harus Login Untuk Melihat Halaman
admin_password=Kata Sandi admin_password=Kata Sandi
admin_email=Alamat Email admin_email=Alamat Email
email_title = Pengaturan email
smtp_from = Kirim Email Sebagai
[home] [home]
uname_holder=Nama Pengguna atau Alamat Surel uname_holder=Nama Pengguna atau Alamat Surel
password_holder=Kata Sandi password_holder=Kata Sandi
@ -141,6 +222,8 @@ show_private=Pribadi
issues.in_your_repos=Dalam repositori anda issues.in_your_repos=Dalam repositori anda
show_archived = Diarsipkan
[explore] [explore]
repos=Repositori repos=Repositori
users=Pengguna users=Pengguna
@ -492,6 +575,8 @@ email_notifications.submit=Pasang Pengaturan Email
visibility.private=Pribadi visibility.private=Pribadi
visibility.public = Publik
[repo] [repo]
owner=Pemilik owner=Pemilik
repo_name=Nama Repositori repo_name=Nama Repositori
@ -955,6 +1040,12 @@ branch.deleted_by=Dihapus oleh %s
desc.public = Publik
desc.archived = Diarsipkan
commitstatus.error = Gangguan
projects.new = Proyek Baru
milestones.filter_sort.name = Nama
[graphs] [graphs]
[org] [org]
@ -1009,6 +1100,8 @@ teams.delete_team_success=Tim sudah di hapus.
teams.repositories=Tim repositori teams.repositories=Tim repositori
teams.search_repo_placeholder=Cari repositori… teams.search_repo_placeholder=Cari repositori…
settings.visibility.public = Publik
[admin] [admin]
dashboard=Dasbor dashboard=Dasbor
organizations=Organisasi organizations=Organisasi
@ -1267,6 +1360,9 @@ notices.op=Op.
notices.delete_success=Laporan sistem telah dihapus. notices.delete_success=Laporan sistem telah dihapus.
config_settings = Pengaturan
users.list_status_filter.menu_text = Saring
[action] [action]
create_repo=repositori dibuat <a href="%s">%s</a> create_repo=repositori dibuat <a href="%s">%s</a>
rename_repo=ganti nama gudang penyimpanan dari <code>%[1]s</code> ke <a href="%[2]s">%[3]s</a> rename_repo=ganti nama gudang penyimpanan dari <code>%[1]s</code> ke <a href="%[2]s">%[3]s</a>
@ -1342,6 +1438,56 @@ runs.commit=Memperbuat
runs.no_matching_online_runner_helper = Tidak ada runner online yang cocok dengan label: %s
runs.actor = Aktor
runs.status = Status
runs.actors_no_select = Semua aktor
runs.status_no_select = Semua status
runs.no_results = Tidak ada hasil yang cocok.
runs.no_workflows = Belum ada alur kerja.
runs.no_runs = Alur kerja belum berjalan.
runs.empty_commit_message = (pesan commit kosong)
workflow.disable = Nonaktifkan Alur Kerja
workflow.enable = Aktifkan Alur Kerja
workflow.disabled = Alur kerja dinonaktifkan.
need_approval_desc = Butuh persetujuan untuk menjalankan alur kerja untuk pull request fork.
variables = Variabel
variables.creation = Tambah Variabel
variables.none = Belum ada variabel.
variables.deletion = Hapus variabel
variables.deletion.description = Menghapus variabel bersifat permanen dan tidak dapat dibatalkan. Lanjutkan?
variables.description = Variabel akan diteruskan ke beberapa tindakan dan tidak dapat dibaca sebaliknya.
variables.id_not_exist = Variabel dengan ID %d tidak ada.
variables.edit = Edit Variabel
variables.deletion.failed = Gagal menghapus variabel.
variables.deletion.success = Variabel telah dihapus.
variables.creation.failed = Gagal menambahkan variabel.
variables.creation.success = Variabel "%s" telah ditambahkan.
variables.update.failed = Gagal mengedit variabel.
variables.update.success = Variabel telah diedit.
[projects] [projects]
type-1.display_name = Proyek Individu
type-2.display_name = Proyek Repositori
type-3.display_name = Proyek Organisasi
[git.filemode] [git.filemode]
changed_filemode = %[1]s → %[2]s
directory = Directory
normal_file = Normal file
executable_file = Executable file
symbolic_link = Symbolic link
submodule = Submodule
[search]
search = Cari...
type_tooltip = Tipe pencarian
fuzzy_tooltip = Termasuk juga hasil yang mendekati kata pencarian
exact_tooltip = Hanya menampilkan hasil yang cocok dengan istilah pencarian
repo_kind = Cari repo...
user_kind = Telusuri pengguna...
org_kind = Cari organisasi...
team_kind = Cari tim...
code_kind = Cari kode...
code_search_unavailable = Pencarian kode saat ini tidak tersedia. Silahkan hubungi administrator.
branch_kind = Cari cabang...

View file

@ -114,6 +114,10 @@ value=Gildi
sign_in_with_provider = Skrá inn með %s sign_in_with_provider = Skrá inn með %s
enable_javascript = Þessi síða krefst JavaScript. enable_javascript = Þessi síða krefst JavaScript.
filter = Sía
filter.is_archived = Safnvistað
filter.public = Opinbert
[aria] [aria]
[heatmap] [heatmap]
@ -1118,6 +1122,8 @@ topic.done=Í lagi
milestones.filter_sort.name = Heiti
[graphs] [graphs]
[org] [org]
@ -1287,6 +1293,9 @@ notices.type_2=Verkefni
notices.desc=Lýsing notices.desc=Lýsing
config_summary = Yfirlit
config_settings = Stillingar
[action] [action]
create_issue=`opnaði vandamál <a href="%[1]s">%[3]s#%[2]s</a>` create_issue=`opnaði vandamál <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue=`enduropnaði vandamál <a href="%[1]s">%[3]s#%[2]s</a>` reopen_issue=`enduropnaði vandamál <a href="%[1]s">%[3]s#%[2]s</a>`
@ -1371,3 +1380,5 @@ runs.commit=Framlag
[projects] [projects]
[git.filemode] [git.filemode]
[search]

View file

@ -167,6 +167,8 @@ new_org.link = 新しい組織
test = テスト test = テスト
error413 = 割り当て量を使い切りしました。 error413 = 割り当て量を使い切りしました。
copy_path = パスをコピー
[aria] [aria]
navbar=ナビゲーションバー navbar=ナビゲーションバー
footer=フッター footer=フッター
@ -3515,6 +3517,8 @@ config.app_slogan = インスタンスのスローガン
config.cache_test = テストキャッシュ config.cache_test = テストキャッシュ
config.cache_test_failed = キャッシュの調査に失敗しました: %v.
[action] [action]
create_repo=がリポジトリ <a href="%s">%s</a> を作成しました create_repo=がリポジトリ <a href="%s">%s</a> を作成しました
rename_repo=がリポジトリ名を <code>%[1]s</code> から <a href="%[2]s">%[3]s</a> へ変更しました rename_repo=がリポジトリ名を <code>%[1]s</code> から <a href="%[2]s">%[3]s</a> へ変更しました
@ -3758,6 +3762,8 @@ rpm.repository.multiple_groups = このパッケージは複数のグループ
owner.settings.cargo.rebuild.no_index = 再構築できません、インデックスが初期化されていません。 owner.settings.cargo.rebuild.no_index = 再構築できません、インデックスが初期化されていません。
npm.dependencies.bundle = バンドルされた依存関係 npm.dependencies.bundle = バンドルされた依存関係
search_in_external_registry = %s で検索
[secrets] [secrets]
secrets=シークレット secrets=シークレット
description=シークレットは特定のActionsに渡されます。 それ以外で読み出されることはありません。 description=シークレットは特定のActionsに渡されます。 それ以外で読み出されることはありません。
@ -3875,11 +3881,15 @@ workflow.dispatch.invalid_input_type = 入力タイプ「%s」が無効です。
workflow.dispatch.warn_input_limit = 最初の %d 個の入力のみを表示します。 workflow.dispatch.warn_input_limit = 最初の %d 個の入力のみを表示します。
runs.no_job = ワークフローには少なくとも1つのジョブが含まれている必要があります runs.no_job = ワークフローには少なくとも1つのジョブが含まれている必要があります
runs.expire_log_message = ログは古すぎるため消去されています。
[projects] [projects]
type-1.display_name=個人プロジェクト type-1.display_name=個人プロジェクト
type-2.display_name=リポジトリ プロジェクト type-2.display_name=リポジトリ プロジェクト
type-3.display_name=組織プロジェクト type-3.display_name=組織プロジェクト
deleted.display_name = 削除されたプロジェクト
[git.filemode] [git.filemode]
changed_filemode=%[1]s → %[2]s changed_filemode=%[1]s → %[2]s
directory=ディレクトリ directory=ディレクトリ

View file

@ -376,7 +376,7 @@ allow_password_change=사용자에게 비밀번호 변경을 요청 (권장됨)
reset_password_mail_sent_prompt=확인 메일이 <b>%s</b>로 전송되었습니다. 받은 편지함으로 도착한 메일을 %s 안에 확인해서 비밀번호 찾기 절차를 완료하십시오. reset_password_mail_sent_prompt=확인 메일이 <b>%s</b>로 전송되었습니다. 받은 편지함으로 도착한 메일을 %s 안에 확인해서 비밀번호 찾기 절차를 완료하십시오.
active_your_account=계정 활성화 active_your_account=계정 활성화
account_activated=계정이 활성화 되었습니다 account_activated=계정이 활성화 되었습니다
prohibit_login = prohibit_login =
resent_limit_prompt=활성화를 위한 이메일을 이미 전송했습니다. 3분 내로 이메일을 받지 못한 경우 재시도해주세요. resent_limit_prompt=활성화를 위한 이메일을 이미 전송했습니다. 3분 내로 이메일을 받지 못한 경우 재시도해주세요.
has_unconfirmed_mail=안녕하세요 %s, 이메일 주소(<b>%s</b>)가 확인되지 않았습니다. 확인 메일을 받으시지 못하겼거나 새로운 확인 메일이 필요하다면, 아래 버튼을 클릭해 재발송하실 수 있습니다. has_unconfirmed_mail=안녕하세요 %s, 이메일 주소(<b>%s</b>)가 확인되지 않았습니다. 확인 메일을 받으시지 못하겼거나 새로운 확인 메일이 필요하다면, 아래 버튼을 클릭해 재발송하실 수 있습니다.
resend_mail=여기를 눌러 확인 메일 재전송 resend_mail=여기를 눌러 확인 메일 재전송
@ -1425,6 +1425,8 @@ archive.title_date = 이 저장소는 %s에 보관처리되었습니다. 파일
milestones.filter_sort.name = 이름
[graphs] [graphs]
[org] [org]
@ -1797,6 +1799,9 @@ emails.filter_sort.name_reverse = 사용자명 (예약됨)
config.allow_dots_in_usernames = 사용자들이 마침표를 사용자명에 사용할 수 있도록 허가합니다. 이미 존재하는 계정에는 영향을 주지 않습니다. config.allow_dots_in_usernames = 사용자들이 마침표를 사용자명에 사용할 수 있도록 허가합니다. 이미 존재하는 계정에는 영향을 주지 않습니다.
config_summary = 요약
config_settings = 설정
[action] [action]
create_repo=저장소를 만들었습니다. <a href="%s">%s</a> create_repo=저장소를 만들었습니다. <a href="%s">%s</a>
rename_repo=저장소 이름을 <code>%[1]s에서</code>에서 <a href="%[2]s"> %[3]s</a>으로 변경함 rename_repo=저장소 이름을 <code>%[1]s에서</code>에서 <a href="%[2]s"> %[3]s</a>으로 변경함

View file

@ -101,6 +101,11 @@ concept_user_organization=සංවිධානය
name=නම name=නම
filter = පෙරහන
filter.is_archived = සංරක්ෂිත
filter.public = ප්‍රසිද්ධ
filter.private = පෞද්ගලික
[aria] [aria]
[heatmap] [heatmap]
@ -1913,6 +1918,8 @@ error.csv.too_large=එය ඉතා විශාල නිසා මෙම ග
error.csv.unexpected=%d පේළියේ සහ %dතීරුවේ අනපේක්ෂිත චරිතයක් අඩංගු බැවින් මෙම ගොනුව විදැහුම්කරණය කළ නොහැක. error.csv.unexpected=%d පේළියේ සහ %dතීරුවේ අනපේක්ෂිත චරිතයක් අඩංගු බැවින් මෙම ගොනුව විදැහුම්කරණය කළ නොහැක.
error.csv.invalid_field_count=මෙම ගොනුව රේඛාවේ වැරදි ක්ෂේත්ර සංඛ්යාවක් ඇති බැවින් එය විදැහුම්කරණය කළ නොහැක %d. error.csv.invalid_field_count=මෙම ගොනුව රේඛාවේ වැරදි ක්ෂේත්ර සංඛ්යාවක් ඇති බැවින් එය විදැහුම්කරණය කළ නොහැක %d.
milestones.filter_sort.name = නම
[graphs] [graphs]
[org] [org]
@ -2462,6 +2469,9 @@ notices.op=ඔප්.
notices.delete_success=පද්ධති දැන්වීම් මකා දමා ඇත. notices.delete_success=පද්ධති දැන්වීම් මකා දමා ඇත.
config_summary = සාරාංශය
config_settings = සැකසුම්
[action] [action]
create_repo=නිර්මිත ගබඩාව <a href="%s">%s</a> create_repo=නිර්මිත ගබඩාව <a href="%s">%s</a>
rename_repo=<code>%[1]s</code> සිට <a href="%[2]s">%[3]s</a>දක්වා නම් කරන ලද ගබඩාව rename_repo=<code>%[1]s</code> සිට <a href="%[2]s">%[3]s</a>දක්වා නම් කරන ලද ගබඩාව
@ -2554,3 +2564,5 @@ runs.commit=කැප
[git.filemode] [git.filemode]
symbolic_link=සංකේතාත්මක සබැඳිය symbolic_link=සංකේතාත්මක සබැඳිය
[search]

View file

@ -142,6 +142,9 @@ name=Meno
value=Hodnota value=Hodnota
issues = Problémy issues = Problémy
filter.is_archived = Archivované
filter.private = Súkromný
[aria] [aria]
navbar=Navigačná lišta navbar=Navigačná lišta
footer=Päta footer=Päta
@ -1370,4 +1373,6 @@ runners.labels=Štítky
[projects] [projects]
[git.filemode] [git.filemode]
symbolic_link=Symbolický odkaz symbolic_link=Symbolický odkaz
[search]

View file

@ -342,6 +342,8 @@ app_slogan_helper = Oluşum sloganınızı giriniz. Devre dışı bırakmak içi
enable_update_checker_helper_forgejo = release.forgejo.org adresindeki TXT DNS kayıdı kullanılarak yeni Forgejo sürümleri düzenli olarak kontrol edilecektir. enable_update_checker_helper_forgejo = release.forgejo.org adresindeki TXT DNS kayıdı kullanılarak yeni Forgejo sürümleri düzenli olarak kontrol edilecektir.
allow_dots_in_usernames = Kullanıcı isimlerinde noktaya izin ver. Var olan kullanıcıları etkilemez. allow_dots_in_usernames = Kullanıcı isimlerinde noktaya izin ver. Var olan kullanıcıları etkilemez.
smtp_from_invalid = `"E-posta Olarak Gönder" adresi geçersiz`
[home] [home]
uname_holder=Kullanıcı adı veya e-posta adresi uname_holder=Kullanıcı adı veya e-posta adresi
password_holder=Parola password_holder=Parola
@ -663,6 +665,9 @@ admin_cannot_delete_self = Yöneticiyken kullanıcınızı silemezsiniz. Lütfen
username_error_no_dots = ` sadece alfanumerik karakterler ("0-9","a-z","A-Z"), tire ("-") ve alt tire ("-") içerebilir. Alfanumerik olmayan karakterlerle başlayamaz ve bitemez, ayrıca ardışık alfanumerik olmayan karakterler de kullanılamaz.` username_error_no_dots = ` sadece alfanumerik karakterler ("0-9","a-z","A-Z"), tire ("-") ve alt tire ("-") içerebilir. Alfanumerik olmayan karakterlerle başlayamaz ve bitemez, ayrıca ardışık alfanumerik olmayan karakterler de kullanılamaz.`
unset_password = Oturum açma kullanıcısı parola belirlemedi.
unsupported_login_type = Oturum açma türü hesap silmeyi desteklemiyor.
[user] [user]
change_avatar=Profil resmini değiştir… change_avatar=Profil resmini değiştir…
joined_on=%s tarihinde katıldı joined_on=%s tarihinde katıldı
@ -2692,7 +2697,37 @@ activity.navbar.contributors = Katılımcılar
contributors.contribution_type.deletions = Çıkarmalar contributors.contribution_type.deletions = Çıkarmalar
settings.new_owner_blocked_doer = Yeni sahip sizi engelledi. settings.new_owner_blocked_doer = Yeni sahip sizi engelledi.
open_with_editor = %s ile aç
object_format = Nesne Biçimi
mirror_sync = eşitlendi
stars = Yıldızlar
desc.sha256 = SHA256
vendored = Sağlanmış
generated = Üretilmiş
editor.push_out_of_date = İtme eskimiş.
commits.search_branch = Bu Dal
issues.edit.already_changed = Konuya yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
pulls.edit.already_changed = Değişiklik isteğine yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
pulls.nothing_to_compare_have_tag = Seçili dal/etiket aynı.
pulls.fast_forward_only_merge_pull_request = Sadece ileri sarma
comments.edit.already_changed = Yoruma yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
milestones.filter_sort.name = Ad
activity.navbar.pulse = Eğilim
activity.navbar.code_frequency = Kod Frekansı
activity.navbar.recent_commits = Son İşlemeler
settings.mirror_settings.pushed_repository = İtilmiş depo
settings.ignore_stale_approvals = Eskimiş onayları yoksay
settings.ignore_stale_approvals_desc = Daha eski işlemelere (eski incelemelere) yapılmış olan onayları, Dİ'nin kaç onayı olduğunu belirlerken sayma. Eskimiş incelemeler atıldıysa bu ilgisizdir.
error.broken_git_hook = Bu deponun Git İstemcileri bozuk gibi gözüküyor. Onarmak için lütfen <a target="_blank" rel="noreferrer" href="%s">belgelere</a> bakın, daha sonra durumu yenilemek için bazı işlemeler itin.
[graphs] [graphs]
component_loading = %s yükleniyor...
component_loading_failed = %s yüklenemedi
component_loading_info = Bu biraz sürebilir…
component_failed_to_load = Beklenmedik bir hata oluştu.
code_frequency.what = kod frekansı
contributors.what = katkılar
recent_commits.what = son işlemeler
[org] [org]
org_name_holder=Organizasyon Adı org_name_holder=Organizasyon Adı
@ -3346,6 +3381,23 @@ notices.op=İşlem
notices.delete_success=Sistem bildirimleri silindi. notices.delete_success=Sistem bildirimleri silindi.
self_check = Öz Denetim
config_summary = Özet
config_settings = Ayarlar
dashboard.sync_repo_tags = Etiketleri git verisinden veritabanına eşitle
emails.delete = E-postayı Sil
emails.delete_desc = Bu e-posta adresini silmek istediğinizden emin misiniz?
emails.deletion_success = E-posta adresi silindi.
emails.delete_primary_email_error = Ana e-posta adresini silemezsiniz.
config.cache_test = Önbelleği Sına
config.cache_test_failed = Önbelleğin incelenmesi başarısız oldu: %v.
config.cache_test_slow = Önbellek sınaması başarılı, ancak yanıt yavaş: %s.
config.cache_test_succeeded = Önbellek sınaması başarılı, %s sürede bir yanıt alındı.
config.open_with_editor_app_help = Klon menüsü için "Birlikte aç" düzenleyicileri. Boş bırakılırsa, varsayılan kullanılacaktır. Varsayılanı görmek için genişletin.
self_check.no_problem_found = Henüz bir sorun bulunmadı.
self_check.database_collation_mismatch = Veritabanının şu harmanlamayı kullanmasını bekle: %s
self_check.database_inconsistent_collation_columns = Veritabanı %s harmanlamasını kullanıyor, ancak bu sütunlar uyumsuz harmanlamalar kullanıyor. Bu beklenmedik sorunlar oluşturabilir.
[action] [action]
create_repo=depo <a href="%s">%s</a> oluşturuldu create_repo=depo <a href="%s">%s</a> oluşturuldu
rename_repo=<code>%[1]s</code> olan depo adını <a href="%[2]s">%[3]s</a> buna çevirdi rename_repo=<code>%[1]s</code> olan depo adını <a href="%[2]s">%[3]s</a> buna çevirdi
@ -3586,6 +3638,9 @@ owner.settings.chef.title=Chef Kütüğü
owner.settings.chef.keypair=Anahtar çifti üret owner.settings.chef.keypair=Anahtar çifti üret
owner.settings.chef.keypair.description=Chef kütüğünde kimlik doğrulaması için bir anahtar çifti gereklidir. Eğer daha önce bir anahtar çifti ürettiyseniz, yeni bir anahtar çifti üretmek eski anahtar çiftini ıskartaya çıkartacaktır. owner.settings.chef.keypair.description=Chef kütüğünde kimlik doğrulaması için bir anahtar çifti gereklidir. Eğer daha önce bir anahtar çifti ürettiyseniz, yeni bir anahtar çifti üretmek eski anahtar çiftini ıskartaya çıkartacaktır.
npm.dependencies.bundle = Paketlenmiş Bağımlılıklar
rpm.repository.multiple_groups = Bu paket birçok grupta mevcut.
[secrets] [secrets]
secrets=Gizlilikler secrets=Gizlilikler
description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz. description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz.
@ -3693,6 +3748,10 @@ runs.no_workflows.documentation = Gitea İşlem'i hakkında daha fazla bilgi iç
variables.id_not_exist = %d kimlikli değişken mevcut değil. variables.id_not_exist = %d kimlikli değişken mevcut değil.
runs.no_workflows.quick_start = Gitea İşlem'i nasıl başlatacağınızı bilmiyor musunuz? <a target="_blank" rel="noopener noreferrer" href="%s">Hızlı başlangıç rehberine</a> bakabilirsiniz. runs.no_workflows.quick_start = Gitea İşlem'i nasıl başlatacağınızı bilmiyor musunuz? <a target="_blank" rel="noopener noreferrer" href="%s">Hızlı başlangıç rehberine</a> bakabilirsiniz.
runs.no_job_without_needs = İş akışı en azından bağımlılığı olmayan bir görev içermelidir.
runs.no_job = İş akışı en azından bir görev içermelidir
runs.expire_log_message = Günlükler, çok eski oldukları için temizlendiler.
[projects] [projects]
type-1.display_name=Kişisel Proje type-1.display_name=Kişisel Proje
type-2.display_name=Depo Projesi type-2.display_name=Depo Projesi
@ -3732,3 +3791,5 @@ keyword_search_unavailable = Anahtar kelime ile arama şu anda kullanıma açık
fuzzy_tooltip = Arama terimine yakın olan eşleşmeleri dahil et fuzzy_tooltip = Arama terimine yakın olan eşleşmeleri dahil et
union_tooltip = Boşlukla ayrılmış anahtar kelime eşleşmelerini dahil et union_tooltip = Boşlukla ayrılmış anahtar kelime eşleşmelerini dahil et
exact_tooltip = Sadece arama terimiyle tam uyuşan sonuçları dahit et. exact_tooltip = Sadece arama terimiyle tam uyuşan sonuçları dahit et.
fuzzy = Bulanık
exact = Tam

View file

@ -134,6 +134,8 @@ copy_generic = 複製到剪貼簿
copy_url = 複製網址 copy_url = 複製網址
copy_hash = 複製雜湊值 copy_hash = 複製雜湊值
filter.private = 私有庫
[aria] [aria]
footer = 頁尾 footer = 頁尾
footer.links = 連結 footer.links = 連結
@ -791,6 +793,8 @@ settings.branches.update_default_branch = 更新預設分支
milestones.filter_sort.name = 組織名稱
[graphs] [graphs]
[org] [org]
@ -1076,6 +1080,8 @@ users = 使用者帳戶
defaulthooks = 預設 Webhook defaulthooks = 預設 Webhook
config_settings = 組織設定
[action] [action]
create_repo=建立了儲存庫 <a href="%s">%s</a> create_repo=建立了儲存庫 <a href="%s">%s</a>
rename_repo=重新命名儲存庫 <code>%[1]s</code> 為 <a href="%[2]s">%[3]s</a> rename_repo=重新命名儲存庫 <code>%[1]s</code> 為 <a href="%[2]s">%[3]s</a>
@ -1146,4 +1152,6 @@ runners.labels = 標籤
[projects] [projects]
[git.filemode] [git.filemode]
[search]

View file

@ -2842,6 +2842,17 @@ release.summary_card_alt = 儲存庫 %[2]s 中名為「%[1]s」的發行摘要
error.broken_git_hook = 此儲存庫的 Git 鉤子似乎已損壞。請按照<a target="_blank" rel="noreferrer" href="%s">文件</a>修復它們,然後推送一些提交以重新整理狀態。 error.broken_git_hook = 此儲存庫的 Git 鉤子似乎已損壞。請按照<a target="_blank" rel="noreferrer" href="%s">文件</a>修復它們,然後推送一些提交以重新整理狀態。
issues.reaction.alt_remove = 從留言中移除 %[1]s 的反應。 issues.reaction.alt_remove = 從留言中移除 %[1]s 的反應。
vendored = 已供應
settings.mirror_settings.docs.doc_link_pull_section = 文件中的「從遠端儲存庫拉取」部分。
settings.event_pull_request_review_request_desc = 合併請求審核請求或審核請求已移除。
settings.protect_status_check_patterns_desc = 輸入模式以指定其他分支在合併到受此規則保護的分支前必須通過的狀態檢查。每行指定一個模式,模式不得為空白。
settings.protect_invalid_status_check_pattern = 狀態檢查模式無效: 「%s」。
settings.ignore_stale_approvals_desc = 不計算在較舊提交上進行的核可(過時的審核)作為合併請求的核可數量。如果過時的審核已經被捨棄,則無關緊要。
settings.tags.protection.pattern.description = 您可以使用單一名稱或 glob 模式或正則表達式來匹配多個標籤。詳情請參閱 <a target="_blank" rel="noopener" href="%s">受保護標籤指南</a>。
settings.thread_id = 線程 ID
settings.archive.text = 封存儲存庫將使其完全變為唯讀。它將從儀表板中隱藏。沒有人(甚至包括您!)將能夠進行新的提交,或打開任何問題或合併請求。
diff.comment.add_line_comment = 新增行評論
[graphs] [graphs]
component_loading = 載入中 %s… component_loading = 載入中 %s…
code_frequency.what = 寫程式頻率 code_frequency.what = 寫程式頻率
@ -3536,6 +3547,9 @@ users.admin.description = 授予此使用者透過網頁介面和 API 提供的
auths.tip.gitea = 註冊一個新的 OAuth2 應用程式。指南可在 %s 找到 auths.tip.gitea = 註冊一個新的 OAuth2 應用程式。指南可在 %s 找到
monitor.queue.activeworkers = 活躍工作者
monitor.queue.settings.desc = 集區會根據工作者佇列的阻塞情況動態增長。
[action] [action]
create_repo=建立了儲存庫 <a href="%s">%s</a> create_repo=建立了儲存庫 <a href="%s">%s</a>
rename_repo=重新命名儲存庫 <code>%[1]s</code> 為 <a href="%[2]s">%[3]s</a> rename_repo=重新命名儲存庫 <code>%[1]s</code> 為 <a href="%[2]s">%[3]s</a>

1629
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -85,7 +85,7 @@
"eslint-plugin-vue-scoped-css": "2.9.0", "eslint-plugin-vue-scoped-css": "2.9.0",
"eslint-plugin-wc": "2.2.0", "eslint-plugin-wc": "2.2.0",
"globals": "15.15.0", "globals": "15.15.0",
"happy-dom": "16.8.1", "happy-dom": "17.1.0",
"license-checker-rseidelsohn": "4.4.2", "license-checker-rseidelsohn": "4.4.2",
"markdownlint-cli": "0.44.0", "markdownlint-cli": "0.44.0",
"postcss-html": "1.8.0", "postcss-html": "1.8.0",

12
poetry.lock generated
View file

@ -44,13 +44,13 @@ files = [
[[package]] [[package]]
name = "cssbeautifier" name = "cssbeautifier"
version = "1.15.2" version = "1.15.3"
description = "CSS unobfuscator and beautifier." description = "CSS unobfuscator and beautifier."
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "cssbeautifier-1.15.2-py3-none-any.whl", hash = "sha256:6daf7f6012ff2914092d675793a5a7f602ca36a10159d0cf5c119183004306e0"}, {file = "cssbeautifier-1.15.3-py3-none-any.whl", hash = "sha256:0dcaf5ce197743a79b3a160b84ea58fcbd9e3e767c96df1171e428125b16d410"},
{file = "cssbeautifier-1.15.2.tar.gz", hash = "sha256:02d42ffa6aefaa87f18452b437dbb73f6b98f42e9a84759810dc58f7a6bfc26c"}, {file = "cssbeautifier-1.15.3.tar.gz", hash = "sha256:406b04d09e7d62c0be084fbfa2cba5126fe37359ea0d8d9f7b963a6354fc8303"},
] ]
[package.dependencies] [package.dependencies]
@ -115,13 +115,13 @@ files = [
[[package]] [[package]]
name = "jsbeautifier" name = "jsbeautifier"
version = "1.15.2" version = "1.15.3"
description = "JavaScript unobfuscator and beautifier." description = "JavaScript unobfuscator and beautifier."
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "jsbeautifier-1.15.2-py3-none-any.whl", hash = "sha256:d599aed6dcb0d5431190e5ad7335900d5fdc67236082fe6b6d3fb61d568d7417"}, {file = "jsbeautifier-1.15.3-py3-none-any.whl", hash = "sha256:b207a15ab7529eee4a35ae7790e9ec4e32a2b5026d51e2d0386c3a65e6ecfc91"},
{file = "jsbeautifier-1.15.2.tar.gz", hash = "sha256:6aff11af2c6cb9a2ce135f33a5b223cf5ee676ab7ff5da0edac01e23734f5755"}, {file = "jsbeautifier-1.15.3.tar.gz", hash = "sha256:5f1baf3d4ca6a615bb5417ee861b34b77609eeb12875555f8bbfabd9bf2f3457"},
] ]
[package.dependencies] [package.dependencies]

View file

@ -63,6 +63,7 @@ func UpdateUserSettings(ctx *context.APIContext) {
Theme: optional.FromPtr(form.Theme), Theme: optional.FromPtr(form.Theme),
DiffViewStyle: optional.FromPtr(form.DiffViewStyle), DiffViewStyle: optional.FromPtr(form.DiffViewStyle),
KeepEmailPrivate: optional.FromPtr(form.HideEmail), KeepEmailPrivate: optional.FromPtr(form.HideEmail),
KeepPronounsPrivate: optional.FromPtr(form.HidePronouns),
KeepActivityPrivate: optional.FromPtr(form.HideActivity), KeepActivityPrivate: optional.FromPtr(form.HideActivity),
EnableRepoUnitHints: optional.FromPtr(form.EnableRepoUnitHints), EnableRepoUnitHints: optional.FromPtr(form.EnableRepoUnitHints),
} }

View file

@ -31,6 +31,7 @@ func AuthShared(ctx *context.Base, sessionStore auth_service.SessionStore, authM
ctx.Data["SignedUserID"] = ar.Doer.ID ctx.Data["SignedUserID"] = ar.Doer.ID
ctx.Data["IsAdmin"] = ar.Doer.IsAdmin ctx.Data["IsAdmin"] = ar.Doer.IsAdmin
} else { } else {
ctx.Data["IsSigned"] = false
ctx.Data["SignedUserID"] = int64(0) ctx.Data["SignedUserID"] = int64(0)
} }
return ar, nil return ar, nil

View file

@ -62,7 +62,7 @@ func autoSignIn(ctx *context.Context) (bool, error) {
return false, nil return false, nil
} }
u, err := user_model.VerifyUserAuthorizationToken(ctx, authCookie, auth.LongTermAuthorization, false) u, _, err := user_model.VerifyUserAuthorizationToken(ctx, authCookie, auth.LongTermAuthorization)
if err != nil { if err != nil {
return false, fmt.Errorf("VerifyUserAuthorizationToken: %w", err) return false, fmt.Errorf("VerifyUserAuthorizationToken: %w", err)
} }
@ -672,7 +672,7 @@ func Activate(ctx *context.Context) {
return return
} }
user, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.UserActivation, false) user, deleteToken, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.UserActivation)
if err != nil { if err != nil {
ctx.ServerError("VerifyUserAuthorizationToken", err) ctx.ServerError("VerifyUserAuthorizationToken", err)
return return
@ -693,6 +693,11 @@ func Activate(ctx *context.Context) {
return return
} }
if err := deleteToken(); err != nil {
ctx.ServerError("deleteToken", err)
return
}
handleAccountActivation(ctx, user) handleAccountActivation(ctx, user)
} }
@ -741,7 +746,7 @@ func ActivatePost(ctx *context.Context) {
return return
} }
user, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.UserActivation, true) user, deleteToken, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.UserActivation)
if err != nil { if err != nil {
ctx.ServerError("VerifyUserAuthorizationToken", err) ctx.ServerError("VerifyUserAuthorizationToken", err)
return return
@ -770,6 +775,11 @@ func ActivatePost(ctx *context.Context) {
} }
} }
if err := deleteToken(); err != nil {
ctx.ServerError("deleteToken", err)
return
}
handleAccountActivation(ctx, user) handleAccountActivation(ctx, user)
} }
@ -830,7 +840,7 @@ func ActivateEmail(ctx *context.Context) {
code := ctx.FormString("code") code := ctx.FormString("code")
emailStr := ctx.FormString("email") emailStr := ctx.FormString("email")
u, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.EmailActivation(emailStr), true) u, deleteToken, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.EmailActivation(emailStr))
if err != nil { if err != nil {
ctx.ServerError("VerifyUserAuthorizationToken", err) ctx.ServerError("VerifyUserAuthorizationToken", err)
return return
@ -840,6 +850,11 @@ func ActivateEmail(ctx *context.Context) {
return return
} }
if err := deleteToken(); err != nil {
ctx.ServerError("deleteToken", err)
return
}
email, err := user_model.GetEmailAddressOfUser(ctx, emailStr, u.ID) email, err := user_model.GetEmailAddressOfUser(ctx, emailStr, u.ID)
if err != nil { if err != nil {
ctx.ServerError("GetEmailAddressOfUser", err) ctx.ServerError("GetEmailAddressOfUser", err)

View file

@ -116,7 +116,7 @@ func commonResetPassword(ctx *context.Context, shouldDeleteToken bool) (*user_mo
} }
// Fail early, don't frustrate the user // Fail early, don't frustrate the user
u, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.PasswordReset, shouldDeleteToken) u, deleteToken, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.PasswordReset)
if err != nil { if err != nil {
ctx.ServerError("VerifyUserAuthorizationToken", err) ctx.ServerError("VerifyUserAuthorizationToken", err)
return nil, nil return nil, nil
@ -127,6 +127,13 @@ func commonResetPassword(ctx *context.Context, shouldDeleteToken bool) (*user_mo
return nil, nil return nil, nil
} }
if shouldDeleteToken {
if err := deleteToken(); err != nil {
ctx.ServerError("deleteToken", err)
return nil, nil
}
}
twofa, err := auth.GetTwoFactorByUID(ctx, u.ID) twofa, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil { if err != nil {
if !auth.IsErrTwoFactorNotEnrolled(err) { if !auth.IsErrTwoFactorNotEnrolled(err) {

View file

@ -106,6 +106,7 @@ func ProfilePost(ctx *context.Context) {
Location: optional.Some(form.Location), Location: optional.Some(form.Location),
Visibility: optional.Some(form.Visibility), Visibility: optional.Some(form.Visibility),
KeepActivityPrivate: optional.Some(form.KeepActivityPrivate), KeepActivityPrivate: optional.Some(form.KeepActivityPrivate),
KeepPronounsPrivate: optional.Some(form.KeepPronounsPrivate),
} }
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil { if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
ctx.ServerError("UpdateUser", err) ctx.ServerError("UpdateUser", err)

View file

@ -57,7 +57,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
Created: user.CreatedUnix.AsTime(), Created: user.CreatedUnix.AsTime(),
Restricted: user.IsRestricted, Restricted: user.IsRestricted,
Location: user.Location, Location: user.Location,
Pronouns: user.Pronouns, Pronouns: user.GetPronouns(signed),
Website: user.Website, Website: user.Website,
Description: user.Description, Description: user.Description,
// counter's // counter's
@ -97,6 +97,7 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
Description: user.Description, Description: user.Description,
Theme: user.Theme, Theme: user.Theme,
HideEmail: user.KeepEmailPrivate, HideEmail: user.KeepEmailPrivate,
HidePronouns: user.KeepPronounsPrivate,
HideActivity: user.KeepActivityPrivate, HideActivity: user.KeepActivityPrivate,
DiffViewStyle: user.DiffViewStyle, DiffViewStyle: user.DiffViewStyle,
EnableRepoUnitHints: user.EnableRepoUnitHints, EnableRepoUnitHints: user.EnableRepoUnitHints,

View file

@ -224,6 +224,7 @@ type UpdateProfileForm struct {
Biography string `binding:"MaxSize(255)"` Biography string `binding:"MaxSize(255)"`
Visibility structs.VisibleType Visibility structs.VisibleType
KeepActivityPrivate bool KeepActivityPrivate bool
KeepPronounsPrivate bool
} }
// Validate validates the fields // Validate validates the fields

View file

@ -40,6 +40,7 @@ type UpdateOptions struct {
SetLastLogin bool SetLastLogin bool
RepoAdminChangeTeamAccess optional.Option[bool] RepoAdminChangeTeamAccess optional.Option[bool]
EnableRepoUnitHints optional.Option[bool] EnableRepoUnitHints optional.Option[bool]
KeepPronounsPrivate optional.Option[bool]
} }
func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) error { func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) error {
@ -97,6 +98,12 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
cols = append(cols, "enable_repo_unit_hints") cols = append(cols, "enable_repo_unit_hints")
} }
if opts.KeepPronounsPrivate.Has() {
u.KeepPronounsPrivate = opts.KeepPronounsPrivate.Value()
cols = append(cols, "keep_pronouns_private")
}
if opts.AllowGitHook.Has() { if opts.AllowGitHook.Has() {
u.AllowGitHook = opts.AllowGitHook.Value() u.AllowGitHook = opts.AllowGitHook.Value()

View file

@ -72,7 +72,7 @@
<ul class="list"> <ul class="list">
{{if $hasArchiveLinks}} {{if $hasArchiveLinks}}
<li> <li>
<a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"> <a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow" type="application/zip">
{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP) {{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)
</a> </a>
<div class="tw-mr-1"> <div class="tw-mr-1">
@ -83,7 +83,7 @@
</span> </span>
</li> </li>
<li class="{{if $hasReleaseAttachment}}start-gap{{end}}"> <li class="{{if $hasReleaseAttachment}}start-gap{{end}}">
<a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"> <a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow" type="application/gzip">
{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ) {{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)
</a> </a>
<div class="tw-mr-1"> <div class="tw-mr-1">

View file

@ -16,7 +16,7 @@
</div> </div>
<div class="content tw-break-anywhere profile-avatar-name"> <div class="content tw-break-anywhere profile-avatar-name">
{{if .ContextUser.FullName}}<span class="header text center">{{.ContextUser.FullName}}</span>{{end}} {{if .ContextUser.FullName}}<span class="header text center">{{.ContextUser.FullName}}</span>{{end}}
<span class="username text center">{{.ContextUser.Name}}{{if .ContextUser.Pronouns}} · {{.ContextUser.Pronouns}}{{end}} {{if .IsAdmin}} <span class="username text center">{{.ContextUser.Name}} {{if .ContextUser.GetPronouns .IsSigned}} · {{.ContextUser.GetPronouns .IsSigned}}{{end}} {{if .IsAdmin}}
<a class="muted" href="{{AppSubUrl}}/admin/users/{{.ContextUser.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}"> <a class="muted" href="{{AppSubUrl}}/admin/users/{{.ContextUser.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}">
{{svg "octicon-gear" 18}} {{svg "octicon-gear" 18}}
</a> </a>

View file

@ -27954,6 +27954,10 @@
"type": "boolean", "type": "boolean",
"x-go-name": "HideEmail" "x-go-name": "HideEmail"
}, },
"hide_pronouns": {
"type": "boolean",
"x-go-name": "HidePronouns"
},
"language": { "language": {
"type": "string", "type": "string",
"x-go-name": "Language" "x-go-name": "Language"
@ -28006,6 +28010,10 @@
"type": "boolean", "type": "boolean",
"x-go-name": "HideEmail" "x-go-name": "HideEmail"
}, },
"hide_pronouns": {
"type": "boolean",
"x-go-name": "HidePronouns"
},
"language": { "language": {
"type": "string", "type": "string",
"x-go-name": "Language" "x-go-name": "Language"

View file

@ -120,6 +120,12 @@
{{ctx.Locale.Tr "settings.keep_activity_private"}} {{ctx.Locale.Tr "settings.keep_activity_private"}}
<span class="help">{{ctx.Locale.Tr "settings.keep_activity_private.description" (printf "/%s?tab=activity" .SignedUser.Name)}}</span> <span class="help">{{ctx.Locale.Tr "settings.keep_activity_private.description" (printf "/%s?tab=activity" .SignedUser.Name)}}</span>
</label> </label>
<label>
<input name="keep_pronouns_private" type="checkbox" {{if .SignedUser.KeepPronounsPrivate}}checked{{end}}>
{{ctx.Locale.Tr "settings.keep_pronouns_private"}}
<span class="help">{{ctx.Locale.Tr "settings.keep_pronouns_private.description"}}</span>
</label>
</fieldset> </fieldset>
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_profile"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "settings.update_profile"}}</button>

View file

@ -43,10 +43,17 @@ test('External Release Attachments', async ({page, isMobile}) => {
// Validate release page and click edit // Validate release page and click edit
await expect(page).toHaveURL('/user2/repo2/releases'); await expect(page).toHaveURL('/user2/repo2/releases');
await expect(page.locator('.download[open] li')).toHaveCount(3); await expect(page.locator('.download[open] li')).toHaveCount(3);
await expect(page.locator('.download[open] li:nth-of-type(1)')).toContainText('Source code (ZIP)'); await expect(page.locator('.download[open] li:nth-of-type(1)')).toContainText('Source code (ZIP)');
await expect(page.locator('.download[open] li:nth-of-type(1) span[data-tooltip-content]')).toHaveAttribute('data-tooltip-content', 'This attachment is automatically generated.');
await expect(page.locator('.download[open] li:nth-of-type(1) a')).toHaveAttribute('href', '/user2/repo2/archive/2.0.zip'); await expect(page.locator('.download[open] li:nth-of-type(1) a')).toHaveAttribute('href', '/user2/repo2/archive/2.0.zip');
await expect(page.locator('.download[open] li:nth-of-type(1) a')).toHaveAttribute('type', 'application/zip');
await expect(page.locator('.download[open] li:nth-of-type(2)')).toContainText('Source code (TAR.GZ)'); await expect(page.locator('.download[open] li:nth-of-type(2)')).toContainText('Source code (TAR.GZ)');
await expect(page.locator('.download[open] li:nth-of-type(2) span[data-tooltip-content]')).toHaveAttribute('data-tooltip-content', 'This attachment is automatically generated.');
await expect(page.locator('.download[open] li:nth-of-type(2) a')).toHaveAttribute('href', '/user2/repo2/archive/2.0.tar.gz'); await expect(page.locator('.download[open] li:nth-of-type(2) a')).toHaveAttribute('href', '/user2/repo2/archive/2.0.tar.gz');
await expect(page.locator('.download[open] li:nth-of-type(2) a')).toHaveAttribute('type', 'application/gzip');
await expect(page.locator('.download[open] li:nth-of-type(3)')).toContainText('Test'); await expect(page.locator('.download[open] li:nth-of-type(3)')).toContainText('Test');
await expect(page.locator('.download[open] li:nth-of-type(3) a')).toHaveAttribute('href', 'https://forgejo.org/'); await expect(page.locator('.download[open] li:nth-of-type(3) a')).toHaveAttribute('href', 'https://forgejo.org/');
await save_visual(page); await save_visual(page);

View file

@ -438,8 +438,16 @@ func TestUserHints(t *testing.T) {
func TestUserPronouns(t *testing.T) { func TestUserPronouns(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2") // user1 is admin, using user2 and user10 respectively instead.
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) // This is explicitly mentioned here because of the unconventional
// variable naming scheme.
firstUserSession := loginUser(t, "user2")
firstUserToken := getTokenForLoggedInUser(t, firstUserSession, auth_model.AccessTokenScopeWriteUser)
// This user has the HidePronouns setting enabled.
// Check the fixture!
secondUserSession := loginUser(t, "user10")
secondUserToken := getTokenForLoggedInUser(t, secondUserSession, auth_model.AccessTokenScopeWriteUser)
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true}) adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
adminSession := loginUser(t, adminUser.Name) adminSession := loginUser(t, adminUser.Name)
@ -449,8 +457,10 @@ func TestUserPronouns(t *testing.T) {
t.Run("user", func(t *testing.T) { t.Run("user", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(token) // secondUserToken was chosen arbitrarily and should have no impact.
resp := MakeRequest(t, req, http.StatusOK) // See next comment.
req := NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(secondUserToken)
resp := firstUserSession.MakeRequest(t, req, http.StatusOK)
// We check the raw JSON, because we want to test the response, not // We check the raw JSON, because we want to test the response, not
// what it decodes into. Contents doesn't matter, we're testing the // what it decodes into. Contents doesn't matter, we're testing the
@ -468,16 +478,22 @@ func TestUserPronouns(t *testing.T) {
// what it decodes into. Contents doesn't matter, we're testing the // what it decodes into. Contents doesn't matter, we're testing the
// presence only. // presence only.
assert.Contains(t, resp.Body.String(), `"pronouns":`) assert.Contains(t, resp.Body.String(), `"pronouns":`)
req = NewRequest(t, "GET", "/api/v1/users/user10")
resp = MakeRequest(t, req, http.StatusOK)
// Same deal here.
assert.Contains(t, resp.Body.String(), `"pronouns":`)
}) })
t.Run("user/settings", func(t *testing.T) { t.Run("user/settings", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
// Set pronouns first // Set pronouns first for user2
pronouns := "they/them" pronouns := "they/them"
req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{ req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
Pronouns: &pronouns, Pronouns: &pronouns,
}).AddTokenAuth(token) }).AddTokenAuth(firstUserToken)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
// Verify the response // Verify the response
@ -486,7 +502,7 @@ func TestUserPronouns(t *testing.T) {
assert.Equal(t, pronouns, user.Pronouns) assert.Equal(t, pronouns, user.Pronouns)
// Verify retrieving the settings again // Verify retrieving the settings again
req = NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(token) req = NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(firstUserToken)
resp = MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &user) DecodeJSON(t, resp, &user)
@ -497,22 +513,40 @@ func TestUserPronouns(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
// Set the pronouns for user2 // Set the pronouns for user2
pronouns := "she/her" pronouns := "he/him"
req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{ req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{
Pronouns: &pronouns, Pronouns: &pronouns,
}).AddTokenAuth(adminToken) }).AddTokenAuth(adminToken)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
// Verify the API response // Verify the API response
var user *api.User var user2 *api.User
DecodeJSON(t, resp, &user) DecodeJSON(t, resp, &user2)
assert.Equal(t, pronouns, user.Pronouns) assert.Equal(t, pronouns, user2.Pronouns)
// Verify via user2 too // Verify via user2
req = NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(token) req = NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(firstUserToken)
resp = MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &user) DecodeJSON(t, resp, &user2)
assert.Equal(t, pronouns, user.Pronouns) assert.Equal(t, pronouns, user2.Pronouns) // TODO: This fails for some reason
// Set the pronouns for user10
pronouns = "he/him"
req = NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user10", &api.EditUserOption{
Pronouns: &pronouns,
}).AddTokenAuth(adminToken)
resp = MakeRequest(t, req, http.StatusOK)
// Verify the API response
var user10 *api.User
DecodeJSON(t, resp, &user10)
assert.Equal(t, pronouns, user10.Pronouns)
// Verify via user10
req = NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(secondUserToken)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &user10)
assert.Equal(t, pronouns, user10.Pronouns)
}) })
}) })
@ -520,10 +554,10 @@ func TestUserPronouns(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
// Set the pronouns to a known state via the API // Set the pronouns to a known state via the API
pronouns := "she/her" pronouns := "they/them"
req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{ req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
Pronouns: &pronouns, Pronouns: &pronouns,
}).AddTokenAuth(token) }).AddTokenAuth(firstUserToken)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)
t.Run("profile view", func(t *testing.T) { t.Run("profile view", func(t *testing.T) {
@ -534,14 +568,14 @@ func TestUserPronouns(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
userNameAndPronouns := strings.TrimSpace(htmlDoc.Find(".profile-avatar-name .username").Text()) userNameAndPronouns := strings.TrimSpace(htmlDoc.Find(".profile-avatar-name .username").Text())
assert.Contains(t, userNameAndPronouns, pronouns) assert.NotContains(t, userNameAndPronouns, pronouns)
}) })
t.Run("settings", func(t *testing.T) { t.Run("settings", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user/settings") req := NewRequest(t, "GET", "/user/settings")
resp := session.MakeRequest(t, req, http.StatusOK) resp := firstUserSession.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
// Check that the field is present // Check that the field is present
@ -550,12 +584,12 @@ func TestUserPronouns(t *testing.T) {
assert.Equal(t, pronouns, pronounField) assert.Equal(t, pronouns, pronounField)
// Check that updating the field works // Check that updating the field works
newPronouns := "they/them" newPronouns := "she/her"
req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings"), "_csrf": GetCSRF(t, firstUserSession, "/user/settings"),
"pronouns": newPronouns, "pronouns": newPronouns,
}) })
session.MakeRequest(t, req, http.StatusSeeOther) firstUserSession.MakeRequest(t, req, http.StatusSeeOther)
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
assert.Equal(t, newPronouns, user2.Pronouns) assert.Equal(t, newPronouns, user2.Pronouns)
@ -886,11 +920,27 @@ func TestUserActivate(t *testing.T) {
assert.False(t, authToken.IsExpired()) assert.False(t, authToken.IsExpired())
assert.EqualValues(t, authToken.HashedValidator, auth_model.HashValidator(rawValidator)) assert.EqualValues(t, authToken.HashedValidator, auth_model.HashValidator(rawValidator))
req = NewRequest(t, "POST", "/user/activate?code="+code) t.Run("No password", func(t *testing.T) {
session.MakeRequest(t, req, http.StatusOK) defer tests.PrintCurrentTest(t)()
unittest.AssertNotExistsBean(t, &auth_model.AuthorizationToken{ID: authToken.ID}) req = NewRequest(t, "POST", "/user/activate?code="+code)
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "doesnotexist", IsActive: true}) session.MakeRequest(t, req, http.StatusOK)
unittest.AssertExistsIf(t, true, &auth_model.AuthorizationToken{ID: authToken.ID})
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "doesnotexist"}, "is_active = false")
})
t.Run("With password", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req = NewRequestWithValues(t, "POST", "/user/activate?code="+code, map[string]string{
"password": "examplePassword!1",
})
session.MakeRequest(t, req, http.StatusSeeOther)
unittest.AssertExistsIf(t, false, &auth_model.AuthorizationToken{ID: authToken.ID})
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "doesnotexist"}, "is_active = true")
})
} }
func TestUserPasswordReset(t *testing.T) { func TestUserPasswordReset(t *testing.T) {

View file

@ -132,16 +132,16 @@
} }
}, },
"node_modules/@octokit/core": { "node_modules/@octokit/core": {
"version": "6.1.3", "version": "6.1.4",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.3.tgz", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz",
"integrity": "sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==", "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@octokit/auth-token": "^5.0.0", "@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.1.2", "@octokit/graphql": "^8.1.2",
"@octokit/request": "^9.1.4", "@octokit/request": "^9.2.1",
"@octokit/request-error": "^6.1.6", "@octokit/request-error": "^6.1.7",
"@octokit/types": "^13.6.2", "@octokit/types": "^13.6.2",
"before-after-hook": "^3.0.2", "before-after-hook": "^3.0.2",
"universal-user-agent": "^7.0.0" "universal-user-agent": "^7.0.0"
@ -161,9 +161,9 @@
} }
}, },
"node_modules/@octokit/core/node_modules/@octokit/endpoint": { "node_modules/@octokit/core/node_modules/@octokit/endpoint": {
"version": "10.1.2", "version": "10.1.3",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
"integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", "integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -182,14 +182,14 @@
"peer": true "peer": true
}, },
"node_modules/@octokit/core/node_modules/@octokit/request": { "node_modules/@octokit/core/node_modules/@octokit/request": {
"version": "9.2.0", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
"integrity": "sha512-kXLfcxhC4ozCnAXy2ff+cSxpcF0A1UqxjvYMqNuPIeOAzJbVWQ+dy5G2fTylofB/gTbObT8O6JORab+5XtA1Kw==", "integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@octokit/endpoint": "^10.0.0", "@octokit/endpoint": "^10.1.3",
"@octokit/request-error": "^6.0.1", "@octokit/request-error": "^6.1.7",
"@octokit/types": "^13.6.2", "@octokit/types": "^13.6.2",
"fast-content-type-parse": "^2.0.0", "fast-content-type-parse": "^2.0.0",
"universal-user-agent": "^7.0.2" "universal-user-agent": "^7.0.2"
@ -199,9 +199,9 @@
} }
}, },
"node_modules/@octokit/core/node_modules/@octokit/request-error": { "node_modules/@octokit/core/node_modules/@octokit/request-error": {
"version": "6.1.6", "version": "6.1.7",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz",
"integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", "integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -253,13 +253,13 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/@octokit/graphql": { "node_modules/@octokit/graphql": {
"version": "8.2.0", "version": "8.2.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz",
"integrity": "sha512-gejfDywEml/45SqbWTWrhfwvLBrcGYhOn50sPOjIeVvH6i7D16/9xcFA8dAJNp2HMcd+g4vru41g4E2RBiZvfQ==", "integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@octokit/request": "^9.1.4", "@octokit/request": "^9.2.2",
"@octokit/types": "^13.8.0", "@octokit/types": "^13.8.0",
"universal-user-agent": "^7.0.0" "universal-user-agent": "^7.0.0"
}, },
@ -268,9 +268,9 @@
} }
}, },
"node_modules/@octokit/graphql/node_modules/@octokit/endpoint": { "node_modules/@octokit/graphql/node_modules/@octokit/endpoint": {
"version": "10.1.2", "version": "10.1.3",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
"integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", "integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -289,14 +289,14 @@
"peer": true "peer": true
}, },
"node_modules/@octokit/graphql/node_modules/@octokit/request": { "node_modules/@octokit/graphql/node_modules/@octokit/request": {
"version": "9.2.0", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
"integrity": "sha512-kXLfcxhC4ozCnAXy2ff+cSxpcF0A1UqxjvYMqNuPIeOAzJbVWQ+dy5G2fTylofB/gTbObT8O6JORab+5XtA1Kw==", "integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@octokit/endpoint": "^10.0.0", "@octokit/endpoint": "^10.1.3",
"@octokit/request-error": "^6.0.1", "@octokit/request-error": "^6.1.7",
"@octokit/types": "^13.6.2", "@octokit/types": "^13.6.2",
"fast-content-type-parse": "^2.0.0", "fast-content-type-parse": "^2.0.0",
"universal-user-agent": "^7.0.2" "universal-user-agent": "^7.0.2"
@ -306,9 +306,9 @@
} }
}, },
"node_modules/@octokit/graphql/node_modules/@octokit/request-error": { "node_modules/@octokit/graphql/node_modules/@octokit/request-error": {
"version": "6.1.6", "version": "6.1.7",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz",
"integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", "integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -477,6 +477,16 @@
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@types/expect": { "node_modules/@types/expect": {
"version": "1.20.4", "version": "1.20.4",
"resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz",
@ -484,9 +494,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.13.1", "version": "22.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
"integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.20.0" "undici-types": "~6.20.0"
@ -1201,9 +1211,9 @@
} }
}, },
"node_modules/call-bind-apply-helpers": { "node_modules/call-bind-apply-helpers": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -1239,9 +1249,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001699", "version": "1.0.30001700",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
"integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -1995,9 +2005,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.96", "version": "1.5.101",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.96.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz",
"integrity": "sha512-8AJUW6dh75Fm/ny8+kZKJzI1pgoE8bKLZlzDU2W1ENd+DXKJrx7I7l9hb8UWR4ojlnb5OlixMt00QWiYJoVw1w==", "integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
@ -4967,18 +4977,18 @@
} }
}, },
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "4.0.2", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
}, },
"engines": {
"node": "20 || >=22"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
} }
}, },
"node_modules/jquery": { "node_modules/jquery": {
@ -4988,14 +4998,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-beautify": { "node_modules/js-beautify": {
"version": "1.15.2", "version": "1.15.3",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.2.tgz", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.3.tgz",
"integrity": "sha512-mcG6CHJxxih+EFAbd5NEBwrosIs6MoJmiNLFYN6kj5SeJMf7n29Ii/H4lt6zGTvmdB9AApuj5cs4zydjuLeqjw==", "integrity": "sha512-rKKGuyTxGNlyN4EQKWzNndzXpi0bOl8Gl8YQAW1as/oMz0XhD6sHJO1hTvoBDOSzKuJb9WkwoAb34FfdkKMv2A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"config-chain": "^1.1.13", "config-chain": "^1.1.13",
"editorconfig": "^1.0.4", "editorconfig": "^1.0.4",
"glob": "^11.0.0", "glob": "^10.4.2",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"nopt": "^8.0.0" "nopt": "^8.0.0"
}, },
@ -5018,38 +5028,35 @@
} }
}, },
"node_modules/js-beautify/node_modules/glob": { "node_modules/js-beautify/node_modules/glob": {
"version": "11.0.1", "version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^4.0.1", "jackspeak": "^3.1.2",
"minimatch": "^10.0.0", "minimatch": "^9.0.4",
"minipass": "^7.1.2", "minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0", "package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0" "path-scurry": "^1.11.1"
}, },
"bin": { "bin": {
"glob": "dist/esm/bin.mjs" "glob": "dist/esm/bin.mjs"
}, },
"engines": {
"node": "20 || >=22"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/js-beautify/node_modules/minimatch": { "node_modules/js-beautify/node_modules/minimatch": {
"version": "10.0.1", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
"engines": { "engines": {
"node": "20 || >=22" "node": ">=16 || 14 >=14.17"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
@ -5481,13 +5488,10 @@
} }
}, },
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "11.0.2", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC", "license": "ISC"
"engines": {
"node": "20 || >=22"
}
}, },
"node_modules/macos-release": { "node_modules/macos-release": {
"version": "2.5.1", "version": "2.5.1",
@ -6467,16 +6471,16 @@
} }
}, },
"node_modules/path-scurry": { "node_modules/path-scurry": {
"version": "2.0.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"lru-cache": "^11.0.0", "lru-cache": "^10.2.0",
"minipass": "^7.1.2" "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
}, },
"engines": { "engines": {
"node": "20 || >=22" "node": ">=16 || 14 >=14.18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"

View file

@ -30,7 +30,6 @@ const sfc = {
intervalID: null, intervalID: null,
currentJobStepsStates: [], currentJobStepsStates: [],
artifacts: [], artifacts: [],
onHoverRerunIndex: -1,
menuVisible: false, menuVisible: false,
isFullScreen: false, isFullScreen: false,
timeVisible: { timeVisible: {
@ -457,13 +456,13 @@ export function initRepositoryActionView() {
<div class="action-view-left"> <div class="action-view-left">
<div class="job-group-section"> <div class="job-group-section">
<div class="job-brief-list"> <div class="job-brief-list">
<a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id" @mouseenter="onHoverRerunIndex = job.id" @mouseleave="onHoverRerunIndex = -1"> <a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id">
<div class="job-brief-item-left"> <div class="job-brief-item-left">
<ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/> <ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/>
<span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span> <span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span>
</div> </div>
<span class="job-brief-item-right"> <span class="job-brief-item-right">
<SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun && onHoverRerunIndex === job.id"/> <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-3 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun"/>
<span class="step-summary-duration">{{ job.duration }}</span> <span class="step-summary-duration">{{ job.duration }}</span>
</span> </span>
</a> </a>