From 4500757acd3fb3c5f4fea94ee71247eedc4013d6 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Mon, 9 Dec 2024 18:59:11 +0300 Subject: [PATCH 0001/1052] feat: add synchronization for SSH keys with OpenID Connect Co-authored-by: Kirill Kolmykov --- routers/web/admin/auths.go | 1 + services/auth/source/oauth2/providers_base.go | 4 + .../auth/source/oauth2/providers_openid.go | 4 + services/auth/source/oauth2/source.go | 26 ++++-- services/auth/source/oauth2/source_sync.go | 92 +++++++++++++++++++ services/forms/auth_form.go | 1 + templates/admin/auth/edit.tmpl | 25 +++-- templates/admin/auth/source/oauth.tmpl | 26 ++++-- web_src/js/features/admin/common.js | 6 +- 9 files changed, 158 insertions(+), 27 deletions(-) diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 799b7e8a84..dcdc8e6a2a 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -197,6 +197,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { CustomURLMapping: customURLMapping, IconURL: form.Oauth2IconURL, Scopes: scopes, + AttributeSSHPublicKey: form.Oauth2AttributeSSHPublicKey, RequiredClaimName: form.Oauth2RequiredClaimName, RequiredClaimValue: form.Oauth2RequiredClaimValue, SkipLocalTwoFA: form.SkipLocalTwoFA, diff --git a/services/auth/source/oauth2/providers_base.go b/services/auth/source/oauth2/providers_base.go index 9d4ab106e5..63318b84ef 100644 --- a/services/auth/source/oauth2/providers_base.go +++ b/services/auth/source/oauth2/providers_base.go @@ -48,4 +48,8 @@ func (b *BaseProvider) CustomURLSettings() *CustomURLSettings { return nil } +func (b *BaseProvider) CanProvideSSHKeys() bool { + return false +} + var _ Provider = &BaseProvider{} diff --git a/services/auth/source/oauth2/providers_openid.go b/services/auth/source/oauth2/providers_openid.go index 285876d5ac..f606581271 100644 --- a/services/auth/source/oauth2/providers_openid.go +++ b/services/auth/source/oauth2/providers_openid.go @@ -51,6 +51,10 @@ func (o *OpenIDProvider) CustomURLSettings() *CustomURLSettings { return nil } +func (o *OpenIDProvider) CanProvideSSHKeys() bool { + return true +} + var _ GothProvider = &OpenIDProvider{} func init() { diff --git a/services/auth/source/oauth2/source.go b/services/auth/source/oauth2/source.go index 3454c9ad55..fe4823e778 100644 --- a/services/auth/source/oauth2/source.go +++ b/services/auth/source/oauth2/source.go @@ -4,6 +4,8 @@ package oauth2 import ( + "strings" + "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/json" ) @@ -17,15 +19,16 @@ type Source struct { CustomURLMapping *CustomURLMapping IconURL string - Scopes []string - RequiredClaimName string - RequiredClaimValue string - GroupClaimName string - AdminGroup string - GroupTeamMap string - GroupTeamMapRemoval bool - RestrictedGroup string - SkipLocalTwoFA bool `json:",omitempty"` + Scopes []string + AttributeSSHPublicKey string + RequiredClaimName string + RequiredClaimValue string + GroupClaimName string + AdminGroup string + GroupTeamMap string + GroupTeamMapRemoval bool + RestrictedGroup string + SkipLocalTwoFA bool `json:",omitempty"` // reference to the authSource authSource *auth.Source @@ -41,6 +44,11 @@ func (source *Source) ToDB() ([]byte, error) { return json.Marshal(source) } +// ProvidesSSHKeys returns if this source provides SSH Keys +func (source *Source) ProvidesSSHKeys() bool { + return len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 +} + // SetAuthSource sets the related AuthSource func (source *Source) SetAuthSource(authSource *auth.Source) { source.authSource = authSource diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go index 5e30313c8f..667c0957fc 100644 --- a/services/auth/source/oauth2/source_sync.go +++ b/services/auth/source/oauth2/source_sync.go @@ -5,15 +5,20 @@ package oauth2 import ( "context" + "fmt" "time" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" "github.com/markbates/goth" + "github.com/markbates/goth/providers/openidConnect" "golang.org/x/oauth2" + + asymkey_model "code.gitea.io/gitea/models/asymkey" ) // Sync causes this OAuth2 source to synchronize its users with the db. @@ -108,7 +113,94 @@ func (source *Source) refresh(ctx context.Context, provider goth.Provider, u *us u.RefreshToken = token.RefreshToken } + needUserFetch := source.ProvidesSSHKeys() + + if needUserFetch { + fetchedUser, err := fetchUser(provider, token) + if err != nil { + log.Error("fetchUser: %v", err) + } else { + err = updateSSHKeys(ctx, source, user, &fetchedUser) + if err != nil { + log.Error("updateSshKeys: %v", err) + } + } + } + err = user_model.UpdateExternalUserByExternalID(ctx, u) return err } + +func fetchUser(provider goth.Provider, token *oauth2.Token) (goth.User, error) { + state, err := util.CryptoRandomString(40) + if err != nil { + return goth.User{}, err + } + + session, err := provider.BeginAuth(state) + if err != nil { + return goth.User{}, err + } + + if s, ok := session.(*openidConnect.Session); ok { + s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresAt = token.Expiry + s.IDToken = token.Extra("id_token").(string) + } + + gothUser, err := provider.FetchUser(session) + if err != nil { + return goth.User{}, err + } + + return gothUser, nil +} + +func updateSSHKeys( + ctx context.Context, + source *Source, + user *user_model.User, + fetchedUser *goth.User, +) error { + if source.ProvidesSSHKeys() { + sshKeys, err := getSSHKeys(source, fetchedUser) + if err != nil { + return err + } + + if asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sshKeys) { + err = asymkey_model.RewriteAllPublicKeys(ctx) + if err != nil { + return err + } + } + } + + return nil +} + +func getSSHKeys(source *Source, gothUser *goth.User) ([]string, error) { + key := source.AttributeSSHPublicKey + value, exists := gothUser.RawData[key] + if !exists { + return nil, fmt.Errorf("attribute '%s' not found in user data", key) + } + + rawSlice, ok := value.([]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected type for SSH public key, expected []interface{} but got %T", value) + } + + sshKeys := make([]string, 0, len(rawSlice)) + for i, v := range rawSlice { + str, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unexpected element type at index %d in SSH public key array, expected string but got %T", i, v) + } + sshKeys = append(sshKeys, str) + } + + return sshKeys, nil +} diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index f0da63155a..39aae51756 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -75,6 +75,7 @@ type AuthenticationForm struct { Oauth2RestrictedGroup string Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"` Oauth2GroupTeamMapRemoval bool + Oauth2AttributeSSHPublicKey string SkipLocalTwoFA bool SSPIAutoCreateUsers bool SSPIAutoActivateUsers bool diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index a8b2049f92..84fefc0484 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -326,19 +326,28 @@ - {{range .OAuth2Providers}}{{if .CustomURLSettings}} - - - - - - - {{end}}{{end}} + {{range .OAuth2Providers}} + {{if .CustomURLSettings}} + + + + + + + {{end}} + {{if .CanProvideSSHKeys}} + + {{end}} + {{end}}
+
+ + +
diff --git a/templates/admin/auth/source/oauth.tmpl b/templates/admin/auth/source/oauth.tmpl index 0560cc8256..7d0a64d269 100644 --- a/templates/admin/auth/source/oauth.tmpl +++ b/templates/admin/auth/source/oauth.tmpl @@ -63,19 +63,27 @@
- {{range .OAuth2Providers}}{{if .CustomURLSettings}} - - - - - - - {{end}}{{end}} - + {{range .OAuth2Providers}} + {{if .CustomURLSettings}} + + + + + + + {{end}} + {{if .CanProvideSSHKeys}} + + {{end}} + {{end}}
+
+ + +
diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js index 1a5bd6e490..a42d8261f1 100644 --- a/web_src/js/features/admin/common.js +++ b/web_src/js/features/admin/common.js @@ -62,7 +62,7 @@ export function initAdminCommon() { } function onOAuth2Change(applyDefaultValues) { - hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url'); + hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url, .oauth2_attribute_ssh_public_key'); for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input[required]')) { input.removeAttribute('required'); } @@ -85,6 +85,10 @@ export function initAdminCommon() { } } } + const canProvideSSHKeys = document.getElementById(`${provider}_canProvideSSHKeys`); + if (canProvideSSHKeys) { + showElem('.oauth2_attribute_ssh_public_key'); + } onOAuth2UseCustomURLChange(applyDefaultValues); } From 47fd9a421fd9d3a8cba87c1574ea3ae3a60f4cdb Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Tue, 10 Dec 2024 07:41:04 +0000 Subject: [PATCH 0002/1052] style: run make fmt --- services/auth/source/oauth2/source_sync.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go index 667c0957fc..b0b08d1a50 100644 --- a/services/auth/source/oauth2/source_sync.go +++ b/services/auth/source/oauth2/source_sync.go @@ -8,6 +8,7 @@ import ( "fmt" "time" + asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -17,8 +18,6 @@ import ( "github.com/markbates/goth" "github.com/markbates/goth/providers/openidConnect" "golang.org/x/oauth2" - - asymkey_model "code.gitea.io/gitea/models/asymkey" ) // Sync causes this OAuth2 source to synchronize its users with the db. @@ -188,7 +187,7 @@ func getSSHKeys(source *Source, gothUser *goth.User) ([]string, error) { return nil, fmt.Errorf("attribute '%s' not found in user data", key) } - rawSlice, ok := value.([]interface{}) + rawSlice, ok := value.([]any) if !ok { return nil, fmt.Errorf("unexpected type for SSH public key, expected []interface{} but got %T", value) } From a87e0d0e46724e7c5fd06bdfc4a7413bc098461c Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Tue, 10 Dec 2024 14:01:02 +0000 Subject: [PATCH 0003/1052] fix: do not rewrite when there are no keys --- services/auth/source/oauth2/source_sync.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go index b0b08d1a50..59590a8bb3 100644 --- a/services/auth/source/oauth2/source_sync.go +++ b/services/auth/source/oauth2/source_sync.go @@ -169,6 +169,10 @@ func updateSSHKeys( return err } + if len(sshKeys) == 0 { + return nil + } + if asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sshKeys) { err = asymkey_model.RewriteAllPublicKeys(ctx) if err != nil { From 7685a1e98e96a2602d7b50cd767d18e0d1d86128 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Sat, 14 Dec 2024 19:36:09 +0100 Subject: [PATCH 0004/1052] feat: Add summary card for repos and releases --- models/repo/release.go | 55 ++- models/repo/release_test.go | 24 ++ models/repo/repo.go | 5 + modules/card/card.go | 34 +- routers/web/repo/card.go | 522 ++++++++++++++++++++++++++++ routers/web/repo/issue.go | 222 ------------ routers/web/repo/release.go | 12 +- routers/web/web.go | 4 +- services/context/repo.go | 2 + templates/base/head_opengraph.tmpl | 24 +- tests/integration/opengraph_test.go | 16 +- 11 files changed, 673 insertions(+), 247 deletions(-) create mode 100644 routers/web/repo/card.go diff --git a/models/repo/release.go b/models/repo/release.go index 38e38c6572..86cb553da0 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -97,13 +97,11 @@ func init() { // LoadAttributes load repo and publisher attributes for a release func (r *Release) LoadAttributes(ctx context.Context) error { - var err error - if r.Repo == nil { - r.Repo, err = GetRepositoryByID(ctx, r.RepoID) - if err != nil { - return err - } + err := r.LoadRepo(ctx) + if err != nil { + return err } + if r.Publisher == nil { r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID) if err != nil { @@ -123,6 +121,18 @@ func (r *Release) LoadAttributes(ctx context.Context) error { return GetReleaseAttachments(ctx, r) } +// LoadRepo load repo attribute for release +func (r *Release) LoadRepo(ctx context.Context) error { + if r.Repo != nil { + return nil + } + + var err error + r.Repo, err = GetRepositoryByID(ctx, r.RepoID) + + return err +} + // LoadArchiveDownloadCount loads the download count for the source archives func (r *Release) LoadArchiveDownloadCount(ctx context.Context) error { var err error @@ -130,6 +140,25 @@ func (r *Release) LoadArchiveDownloadCount(ctx context.Context) error { return err } +// GetTotalDownloadCount returns the summary of all dowlaod count of files attached to the release +func (r *Release) GetTotalDownloadCount(ctx context.Context) (int64, error) { + var archiveCount int64 + if !r.HideArchiveLinks { + _, err := db.GetEngine(ctx).SQL("SELECT SUM(count) FROM repo_archive_download_count WHERE release_id = ?", r.ID).Get(&archiveCount) + if err != nil { + return 0, err + } + } + + var attachmentCount int64 + _, err := db.GetEngine(ctx).SQL("SELECT SUM(download_count) FROM attachment WHERE release_id = ?", r.ID).Get(&attachmentCount) + if err != nil { + return 0, err + } + + return archiveCount + attachmentCount, nil +} + // APIURL the api url for a release. release must have attributes loaded func (r *Release) APIURL() string { return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10) @@ -160,6 +189,20 @@ func (r *Release) Link() string { return r.Repo.Link() + "/releases/tag/" + util.PathEscapeSegments(r.TagName) } +// SummaryCardURL returns the absolute URL to an image providing a summary of the release +func (r *Release) SummaryCardURL() string { + return fmt.Sprintf("%s/releases/summary-card/%d", r.Repo.HTMLURL(), r.ID) +} + +// DisplayName retruns the name of the release +func (r *Release) DisplayName() string { + if r.IsTag && r.Title == "" { + return r.TagName + } + + return r.Title +} + // IsReleaseExist returns true if release with given tag name already exists. func IsReleaseExist(ctx context.Context, repoID int64, tagName string) (bool, error) { if len(tagName) == 0 { diff --git a/models/repo/release_test.go b/models/repo/release_test.go index 4e61a2805d..c8e489b9a7 100644 --- a/models/repo/release_test.go +++ b/models/repo/release_test.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -25,3 +26,26 @@ func TestMigrate_InsertReleases(t *testing.T) { err := InsertReleases(db.DefaultContext, r) require.NoError(t, err) } + +func TestReleaseLoadRepo(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + release := unittest.AssertExistsAndLoadBean(t, &Release{ID: 1}) + assert.Nil(t, release.Repo) + + require.NoError(t, release.LoadRepo(db.DefaultContext)) + + assert.Equal(t, int64(1), release.Repo.ID) +} + +func TestReleaseDisplayName(t *testing.T) { + release := Release{TagName: "TagName"} + + assert.Empty(t, release.DisplayName()) + + release.IsTag = true + assert.Equal(t, "TagName", release.DisplayName()) + + release.Title = "Title" + assert.Equal(t, "Title", release.DisplayName()) +} diff --git a/models/repo/repo.go b/models/repo/repo.go index cd6be48b90..bdf0de2f85 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -327,6 +327,11 @@ func (repo *Repository) HTMLURL() string { return setting.AppURL + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) } +// SummaryCardURL returns the absolute URL to an image providing a summary of the repo +func (repo *Repository) SummaryCardURL() string { + return fmt.Sprintf("%s/-/summary-card", repo.HTMLURL()) +} + // CommitLink make link to by commit full ID // note: won't check whether it's an right id func (repo *Repository) CommitLink(commitID string) (result string) { diff --git a/modules/card/card.go b/modules/card/card.go index bb160d7ea3..dc3fa7e860 100644 --- a/modules/card/card.go +++ b/modules/card/card.go @@ -5,6 +5,7 @@ package card import ( "bytes" + "fmt" "image" "image/color" "io" @@ -35,12 +36,19 @@ type Card struct { Img *image.RGBA Font *truetype.Font Margin int + Width int + Height int } var fontCache = sync.OnceValues(func() (*truetype.Font, error) { return truetype.Parse(goregular.TTF) }) +// DefaultSize returns the default sie for a card +func DefaultSize() (int, int) { + return 1200, 600 +} + // NewCard creates a new card with the given dimensions in pixels func NewCard(width, height int) (*Card, error) { img := image.NewRGBA(image.Rect(0, 0, width, height)) @@ -55,6 +63,8 @@ func NewCard(width, height int) (*Card, error) { Img: img, Font: font, Margin: 0, + Width: width, + Height: height, }, nil } @@ -67,14 +77,14 @@ func (c *Card) Split(vertical bool, percentage int) (*Card, *Card) { mid := (bounds.Dx() * percentage / 100) + bounds.Min.X subleft := c.Img.SubImage(image.Rect(bounds.Min.X, bounds.Min.Y, mid, bounds.Max.Y)).(*image.RGBA) subright := c.Img.SubImage(image.Rect(mid, bounds.Min.Y, bounds.Max.X, bounds.Max.Y)).(*image.RGBA) - return &Card{Img: subleft, Font: c.Font}, - &Card{Img: subright, Font: c.Font} + return &Card{Img: subleft, Font: c.Font, Width: subleft.Bounds().Dx(), Height: subleft.Bounds().Dy()}, + &Card{Img: subright, Font: c.Font, Width: subright.Bounds().Dx(), Height: subright.Bounds().Dy()} } mid := (bounds.Dy() * percentage / 100) + bounds.Min.Y subtop := c.Img.SubImage(image.Rect(bounds.Min.X, bounds.Min.Y, bounds.Max.X, mid)).(*image.RGBA) subbottom := c.Img.SubImage(image.Rect(bounds.Min.X, mid, bounds.Max.X, bounds.Max.Y)).(*image.RGBA) - return &Card{Img: subtop, Font: c.Font}, - &Card{Img: subbottom, Font: c.Font} + return &Card{Img: subtop, Font: c.Font, Width: subtop.Bounds().Dx(), Height: subtop.Bounds().Dy()}, + &Card{Img: subbottom, Font: c.Font, Width: subbottom.Bounds().Dx(), Height: subbottom.Bounds().Dy()} } // SetMargin sets the margins for the card @@ -244,9 +254,14 @@ func (c *Card) fetchExternalImage(url string) (image.Image, bool) { }, } + // Go expects a absolute URL, so we must change a relative to an absolute one + if !strings.Contains(url, "://") { + url = fmt.Sprintf("%s%s", setting.AppURL, strings.TrimPrefix(url, "/")) + } + resp, err := client.Get(url) if err != nil { - log.Warn("error when fetching external image from %s: %w", url, err) + log.Warn("error when fetching external image from %s: %v", url, err) return nil, false } defer resp.Body.Close() @@ -321,3 +336,12 @@ func (c *Card) DrawExternalImage(url string) { } c.DrawImage(image) } + +// DrawRect draws a rect with the given color +func (c *Card) DrawRect(startX, startY, endX, endY int, color color.Color) { + for x := startX; x <= endX; x++ { + for y := startY; y <= endY; y++ { + c.Img.Set(x, y, color) + } + } +} diff --git a/routers/web/repo/card.go b/routers/web/repo/card.go new file mode 100644 index 0000000000..3564d6486c --- /dev/null +++ b/routers/web/repo/card.go @@ -0,0 +1,522 @@ +package repo + +import ( + "bytes" + "encoding/hex" + "fmt" + "image" + "image/color" + "image/png" + "net/http" + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/models/db" + issue_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/card" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/services/context" +) + +// drawUser draws a user avator in a summary card +func drawUser(ctx *context.Context, card *card.Card, user *user_model.User) error { + if user.UseCustomAvatar { + posterAvatarPath := user.CustomAvatarRelativePath() + if posterAvatarPath != "" { + userAvatarFile, err := storage.Avatars.Open(user.CustomAvatarRelativePath()) + if err != nil { + return err + } + userAvatarImage, _, err := image.Decode(userAvatarFile) + if err != nil { + return err + } + card.DrawImage(userAvatarImage) + } + } else { + posterAvatarLink := user.AvatarLinkWithSize(ctx, 256) + card.DrawExternalImage(posterAvatarLink) + } + return nil +} + +// drawRepoIcon draws the repo icon in a summary card +func drawRepoIcon(ctx *context.Context, card *card.Card, repo *repo_model.Repository) error { + repoAvatarPath := repo.CustomAvatarRelativePath() + if repoAvatarPath != "" { + repoAvatarFile, err := storage.RepoAvatars.Open(repoAvatarPath) + if err != nil { + return err + } + repoAvatarImage, _, err := image.Decode(repoAvatarFile) + if err != nil { + return err + } + card.DrawImage(repoAvatarImage) + return nil + } else { + // If the repo didn't have an avatar, fallback to the repo owner's avatar for the right-hand-side icon + err := repo.LoadOwner(ctx) + if err != nil { + return err + } + if repo.Owner != nil { + err = drawUser(ctx, card, repo.Owner) + if err != nil { + return err + } + } + + return nil + } +} + +// hexToColor converts a hex color to a go color +func hexToColor(colorStr string) (*color.RGBA, error) { + colorStr = strings.TrimLeft(colorStr, "#") + + b, err := hex.DecodeString(colorStr) + if err != nil { + return nil, err + } + + if len(b) < 3 { + return nil, fmt.Errorf("expected at least 3 bytes from DecodeString, got %d", len(b)) + } + + color := color.RGBA{b[0], b[1], b[2], 255} + + return &color, nil +} + +func drawLanguagesCard(ctx *context.Context, card *card.Card) error { + languageList, err := repo_model.GetTopLanguageStats(ctx, ctx.Repo.Repository, 5) + if err != nil { + return err + } + if len(languageList) == 0 { + card.DrawRect(0, 0, card.Width, card.Height, color.White) + return nil + } + + currentX := 0 + var langColor *color.RGBA + + for _, lang := range languageList { + langColor, err = hexToColor(lang.Color) + if err != nil { + return err + } + + langWidth := float32(card.Width) * (lang.Percentage / 100) + card.DrawRect(currentX, 0, currentX+int(langWidth), card.Width, langColor) + currentX += int(langWidth) + } + + if currentX < card.Width { + card.DrawRect(currentX, 0, card.Width, card.Height, langColor) + } + + return nil +} + +func drawRepoSummaryCard(ctx *context.Context, repo *repo_model.Repository) (*card.Card, error) { + width, height := card.DefaultSize() + mainCard, err := card.NewCard(width, height) + if err != nil { + return nil, err + } + + contentCard, languageBarCard := mainCard.Split(false, 90) + + contentCard.SetMargin(60) + topSection, bottomSection := contentCard.Split(false, 75) + issueSummary, issueIcon := topSection.Split(true, 80) + repoInfo, issueDescription := issueSummary.Split(false, 30) + + repoInfo.SetMargin(10) + _, err = repoInfo.DrawText(repo.FullName(), color.Black, 56, card.Top, card.Left) + if err != nil { + return nil, err + } + + issueDescription.SetMargin(10) + _, err = issueDescription.DrawText(repo.Description, color.Gray{128}, 36, card.Top, card.Left) + if err != nil { + return nil, err + } + + issueIcon.SetMargin(10) + err = drawRepoIcon(ctx, issueIcon, repo) + if err != nil { + return nil, err + } + + topCountCard, bottomCountCard := bottomSection.Split(false, 50) + + releaseCount, err := db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{ + // only show draft releases for users who can write, read-only users shouldn't see draft releases. + IncludeDrafts: ctx.Repo.CanWrite(unit_model.TypeReleases), + RepoID: ctx.Repo.Repository.ID, + }) + if err != nil { + return nil, err + } + + starsText := ctx.Locale.TrN( + repo.NumStars, + "explore.stars_one", + "explore.stars_few", + repo.NumStars, + ) + forksText := ctx.Locale.TrN( + repo.NumForks, + "explore.forks_one", + "explore.forks_few", + repo.NumForks, + ) + releasesText := ctx.Locale.TrN( + releaseCount, + "repo.activity.title.releases_1", + "repo.activity.title.releases_n", + releaseCount, + ) + + topCountText := fmt.Sprintf("%s โ€ข %s โ€ข %s", starsText, forksText, releasesText) + + topCountCard.SetMargin(10) + _, err = topCountCard.DrawText(topCountText, color.Gray{128}, 36, card.Top, card.Left) + if err != nil { + return nil, err + } + + issuesText := ctx.Locale.TrN( + repo.NumOpenIssues, + "repo.activity.title.issues_1", + "repo.activity.title.issues_n", + repo.NumOpenIssues, + ) + pullRequestsText := ctx.Locale.TrN( + repo.NumOpenPulls, + "repo.activity.title.prs_1", + "repo.activity.title.prs_n", + repo.NumOpenPulls, + ) + + bottomCountText := fmt.Sprintf("%s โ€ข %s", issuesText, pullRequestsText) + + bottomCountCard.SetMargin(10) + _, err = bottomCountCard.DrawText(bottomCountText, color.Gray{128}, 36, card.Top, card.Left) + if err != nil { + return nil, err + } + + err = drawLanguagesCard(ctx, languageBarCard) + if err != nil { + return nil, err + } + + return mainCard, nil +} + +func drawIssueSummaryCard(ctx *context.Context, issue *issue_model.Issue) (*card.Card, error) { + width, height := issue.SummaryCardSize() + mainCard, err := card.NewCard(width, height) + if err != nil { + return nil, err + } + + mainCard.SetMargin(60) + topSection, bottomSection := mainCard.Split(false, 75) + issueSummary, issueIcon := topSection.Split(true, 80) + repoInfo, issueDescription := issueSummary.Split(false, 15) + + repoInfo.SetMargin(10) + _, err = repoInfo.DrawText(fmt.Sprintf("%s - #%d", issue.Repo.FullName(), issue.Index), color.Gray{128}, 36, card.Top, card.Left) + if err != nil { + return nil, err + } + + issueDescription.SetMargin(10) + _, err = issueDescription.DrawText(issue.Title, color.Black, 56, card.Top, card.Left) + if err != nil { + return nil, err + } + + issueIcon.SetMargin(10) + err = drawRepoIcon(ctx, issueIcon, issue.Repo) + if err != nil { + return nil, err + } + + issueStats, issueAttribution := bottomSection.Split(false, 50) + + var state string + if issue.IsPull && issue.PullRequest.HasMerged { + if issue.PullRequest.Status == 3 { + state = ctx.Locale.TrString("repo.pulls.manually_merged") + } else { + state = ctx.Locale.TrString("repo.pulls.merged") + } + } else if issue.IsClosed { + state = ctx.Locale.TrString("repo.issues.closed_title") + } else if issue.IsPull { + if issue.PullRequest.IsWorkInProgress(ctx) { + state = ctx.Locale.TrString("repo.issues.draft_title") + } else { + state = ctx.Locale.TrString("repo.issues.open_title") + } + } else { + state = ctx.Locale.TrString("repo.issues.open_title") + } + state = strings.ToLower(state) + + issueStats.SetMargin(10) + if issue.IsPull { + reviews := map[int64]bool{} + for _, comment := range issue.Comments { + if comment.Review != nil { + reviews[comment.Review.ID] = true + } + } + _, err = issueStats.DrawText( + fmt.Sprintf("%s, %s, %s", + ctx.Locale.TrN( + issue.NumComments, + "repo.issues.num_comments_1", + "repo.issues.num_comments", + issue.NumComments, + ), + ctx.Locale.TrN( + len(reviews), + "repo.issues.num_reviews_one", + "repo.issues.num_reviews_few", + len(reviews), + ), + state, + ), + color.Gray{128}, 36, card.Top, card.Left) + } else { + _, err = issueStats.DrawText( + fmt.Sprintf("%s, %s", + ctx.Locale.TrN( + issue.NumComments, + "repo.issues.num_comments_1", + "repo.issues.num_comments", + issue.NumComments, + ), + state, + ), + color.Gray{128}, 36, card.Top, card.Left) + } + if err != nil { + return nil, err + } + + issueAttributionIcon, issueAttributionText := issueAttribution.Split(true, 8) + issueAttributionText.SetMargin(5) + _, err = issueAttributionText.DrawText( + fmt.Sprintf( + "%s - %s", + issue.Poster.Name, + issue.Created.AsTime().Format(time.DateOnly), + ), + color.Gray{128}, 36, card.Middle, card.Left) + if err != nil { + return nil, err + } + err = drawUser(ctx, issueAttributionIcon, issue.Poster) + if err != nil { + return nil, err + } + + return mainCard, nil +} + +func drawReleaseSummaryCard(ctx *context.Context, release *repo_model.Release) (*card.Card, error) { + width, height := card.DefaultSize() + mainCard, err := card.NewCard(width, height) + if err != nil { + return nil, err + } + + mainCard.SetMargin(60) + topSection, bottomSection := mainCard.Split(false, 75) + releaseSummary, repoIcon := topSection.Split(true, 80) + repoInfo, releaseDescription := releaseSummary.Split(false, 15) + + repoInfo.SetMargin(10) + _, err = repoInfo.DrawText(release.Repo.FullName(), color.Gray{128}, 36, card.Top, card.Left) + if err != nil { + return nil, err + } + + releaseDescription.SetMargin(10) + _, err = releaseDescription.DrawText(release.DisplayName(), color.Black, 56, card.Top, card.Left) + if err != nil { + return nil, err + } + + repoIcon.SetMargin(10) + err = drawRepoIcon(ctx, repoIcon, release.Repo) + if err != nil { + return nil, err + } + + downloadCountCard, releaseDateCard := bottomSection.Split(true, 75) + + downloadCount, err := release.GetTotalDownloadCount(ctx) + if err != nil { + return nil, err + } + + downloadCountText := ctx.Locale.TrN( + strconv.FormatInt(downloadCount, 10), + "repo.release.download_count_one", + "repo.release.download_count_few", + strconv.FormatInt(downloadCount, 10), + ) + + _, err = downloadCountCard.DrawText(string(downloadCountText), color.Gray{128}, 36, card.Bottom, card.Left) + if err != nil { + return nil, err + } + + _, err = releaseDateCard.DrawText(release.CreatedUnix.AsTime().Format(time.DateOnly), color.Gray{128}, 36, card.Bottom, card.Left) + if err != nil { + return nil, err + } + + return mainCard, nil +} + +// checkCardCache checks if a card in cahce and serves it +func checkCardCache(ctx *context.Context, cacheKey string) bool { + cache := cache.GetCache() + pngData, ok := cache.Get(cacheKey).([]byte) + if ok && pngData != nil && len(pngData) > 0 { + ctx.Resp.Header().Set("Content-Type", "image/png") + ctx.Resp.WriteHeader(http.StatusOK) + _, err := ctx.Resp.Write(pngData) + if err != nil { + ctx.ServerError("GetSummaryCard", err) + } + return true + } + + return false +} + +// serveCard server a Crad to the user adds it to the cache +func serveCard(ctx *context.Context, card *card.Card, cacheKey string) { + cache := cache.GetCache() + + // Encode image, store in cache + var imageBuffer bytes.Buffer + err := png.Encode(&imageBuffer, card.Img) + if err != nil { + ctx.ServerError("GetSummaryCard", err) + return + } + imageBytes := imageBuffer.Bytes() + err = cache.Put(cacheKey, imageBytes, setting.CacheService.TTLSeconds()) + if err != nil { + // don't abort serving the image if we just had a cache storage failure + log.Warn("failed to cache issue summary card: %v", err) + } + + // Finish the uncached image response + ctx.Resp.Header().Set("Content-Type", "image/png") + ctx.Resp.WriteHeader(http.StatusOK) + _, err = ctx.Resp.Write(imageBytes) + if err != nil { + ctx.ServerError("GetSummaryCard", err) + return + } +} + +func DrawRepoSummaryCard(ctx *context.Context) { + cacheKey := fmt.Sprintf("summary_card:repo:%s:%d", ctx.Locale.Language(), ctx.Repo.Repository.ID) + + if checkCardCache(ctx, cacheKey) { + return + } + + card, err := drawRepoSummaryCard(ctx, ctx.Repo.Repository) + if err != nil { + ctx.ServerError("drawRepoSummaryCar", err) + return + } + + serveCard(ctx, card, cacheKey) +} + +func DrawIssueSummaryCard(ctx *context.Context) { + issue, err := issue_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if issue_model.IsErrIssueNotExist(err) { + ctx.Error(http.StatusNotFound) + } else { + ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) + } + return + } + + if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) { + ctx.Error(http.StatusNotFound) + return + } + + cacheKey := fmt.Sprintf("summary_card:issue:%s:%d", ctx.Locale.Language(), issue.ID) + + if checkCardCache(ctx, cacheKey) { + return + } + + card, err := drawIssueSummaryCard(ctx, issue) + if err != nil { + ctx.ServerError("drawIssueSummaryCar", err) + return + } + + serveCard(ctx, card, cacheKey) +} + +func DrawReleaseSummaryCard(ctx *context.Context) { + release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":releaseID")) + if err != nil { + if repo_model.IsErrReleaseNotExist(err) { + ctx.NotFound("", nil) + } else { + ctx.ServerError("GetReleaseForRepoByID", err) + } + return + } + + err = release.LoadRepo(ctx) + if err != nil { + ctx.ServerError("LoadRepo", err) + return + } + + cacheKey := fmt.Sprintf("summary_card:release:%s:%d", ctx.Locale.Language(), release.ID) + + if checkCardCache(ctx, cacheKey) { + return + } + + card, err := drawReleaseSummaryCard(ctx, release) + if err != nil { + ctx.ServerError("drawRepoSummaryCar", err) + return + } + + serveCard(ctx, card, cacheKey) +} diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 78fb5e6c01..b86749ec69 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -10,9 +10,6 @@ import ( "errors" "fmt" "html/template" - "image" - "image/color" - "image/png" "math/big" "net/http" "net/url" @@ -34,8 +31,6 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/cache" - "code.gitea.io/gitea/modules/card" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/git" @@ -47,7 +42,6 @@ import ( "code.gitea.io/gitea/modules/optional" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/storage" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates/vars" @@ -2218,222 +2212,6 @@ func GetIssueInfo(ctx *context.Context) { ctx.JSON(http.StatusOK, convert.ToIssue(ctx, ctx.Doer, issue)) } -// GetSummaryCard get an issue of a repository -func GetSummaryCard(ctx *context.Context) { - issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) - if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.Error(http.StatusNotFound) - } else { - ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) - } - return - } - - if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) { - ctx.Error(http.StatusNotFound) - return - } - - cache := cache.GetCache() - cacheKey := fmt.Sprintf("summary_card:issue:%s:%d", ctx.Locale.Language(), issue.ID) - pngData, ok := cache.Get(cacheKey).([]byte) - if ok && pngData != nil && len(pngData) > 0 { - ctx.Resp.Header().Set("Content-Type", "image/png") - ctx.Resp.WriteHeader(http.StatusOK) - _, err = ctx.Resp.Write(pngData) - if err != nil { - ctx.ServerError("GetSummaryCard", err) - } - return - } - - card, err := drawSummaryCard(ctx, issue) - if err != nil { - ctx.ServerError("GetSummaryCard", err) - return - } - - // Encode image, store in cache - var imageBuffer bytes.Buffer - err = png.Encode(&imageBuffer, card.Img) - if err != nil { - ctx.ServerError("GetSummaryCard", err) - return - } - imageBytes := imageBuffer.Bytes() - err = cache.Put(cacheKey, imageBytes, setting.CacheService.TTLSeconds()) - if err != nil { - // don't abort serving the image if we just had a cache storage failure - log.Warn("failed to cache issue summary card: %v", err) - } - - // Finish the uncached image response - ctx.Resp.Header().Set("Content-Type", "image/png") - ctx.Resp.WriteHeader(http.StatusOK) - _, err = ctx.Resp.Write(imageBytes) - if err != nil { - ctx.ServerError("GetSummaryCard", err) - return - } -} - -func drawSummaryCard(ctx *context.Context, issue *issues_model.Issue) (*card.Card, error) { - width, height := issue.SummaryCardSize() - mainCard, err := card.NewCard(width, height) - if err != nil { - return nil, err - } - - mainCard.SetMargin(60) - topSection, bottomSection := mainCard.Split(false, 75) - issueSummary, issueIcon := topSection.Split(true, 80) - repoInfo, issueDescription := issueSummary.Split(false, 15) - - repoInfo.SetMargin(10) - _, err = repoInfo.DrawText(fmt.Sprintf("%s - #%d", issue.Repo.FullName(), issue.Index), color.Gray{128}, 36, card.Top, card.Left) - if err != nil { - return nil, err - } - - issueDescription.SetMargin(10) - _, err = issueDescription.DrawText(issue.Title, color.Black, 56, card.Top, card.Left) - if err != nil { - return nil, err - } - - issueIcon.SetMargin(10) - - repoAvatarPath := issue.Repo.CustomAvatarRelativePath() - if repoAvatarPath != "" { - repoAvatarFile, err := storage.RepoAvatars.Open(repoAvatarPath) - if err != nil { - return nil, err - } - repoAvatarImage, _, err := image.Decode(repoAvatarFile) - if err != nil { - return nil, err - } - issueIcon.DrawImage(repoAvatarImage) - } else { - // If the repo didn't have an avatar, fallback to the repo owner's avatar for the right-hand-side icon - err = issue.Repo.LoadOwner(ctx) - if err != nil { - return nil, err - } - if issue.Repo.Owner != nil { - err = drawUser(ctx, issueIcon, issue.Repo.Owner) - if err != nil { - return nil, err - } - } - } - - issueStats, issueAttribution := bottomSection.Split(false, 50) - - var state string - if issue.IsPull && issue.PullRequest.HasMerged { - if issue.PullRequest.Status == 3 { - state = ctx.Locale.TrString("repo.pulls.manually_merged") - } else { - state = ctx.Locale.TrString("repo.pulls.merged") - } - } else if issue.IsClosed { - state = ctx.Locale.TrString("repo.issues.closed_title") - } else if issue.IsPull { - if issue.PullRequest.IsWorkInProgress(ctx) { - state = ctx.Locale.TrString("repo.issues.draft_title") - } else { - state = ctx.Locale.TrString("repo.issues.open_title") - } - } else { - state = ctx.Locale.TrString("repo.issues.open_title") - } - state = strings.ToLower(state) - - issueStats.SetMargin(10) - if issue.IsPull { - reviews := map[int64]bool{} - for _, comment := range issue.Comments { - if comment.Review != nil { - reviews[comment.Review.ID] = true - } - } - _, err = issueStats.DrawText( - fmt.Sprintf("%s, %s, %s", - ctx.Locale.TrN( - issue.NumComments, - "repo.issues.num_comments_1", - "repo.issues.num_comments", - issue.NumComments, - ), - ctx.Locale.TrN( - len(reviews), - "repo.issues.num_reviews_one", - "repo.issues.num_reviews_few", - len(reviews), - ), - state, - ), - color.Gray{128}, 36, card.Top, card.Left) - } else { - _, err = issueStats.DrawText( - fmt.Sprintf("%s, %s", - ctx.Locale.TrN( - issue.NumComments, - "repo.issues.num_comments_1", - "repo.issues.num_comments", - issue.NumComments, - ), - state, - ), - color.Gray{128}, 36, card.Top, card.Left) - } - if err != nil { - return nil, err - } - - issueAttributionIcon, issueAttributionText := issueAttribution.Split(true, 8) - issueAttributionText.SetMargin(5) - _, err = issueAttributionText.DrawText( - fmt.Sprintf( - "%s - %s", - issue.Poster.Name, - issue.Created.AsTime().Format("2006-01-02"), - ), - color.Gray{128}, 36, card.Middle, card.Left) - if err != nil { - return nil, err - } - err = drawUser(ctx, issueAttributionIcon, issue.Poster) - if err != nil { - return nil, err - } - - return mainCard, nil -} - -func drawUser(ctx *context.Context, card *card.Card, user *user_model.User) error { - if user.UseCustomAvatar { - posterAvatarPath := user.CustomAvatarRelativePath() - if posterAvatarPath != "" { - userAvatarFile, err := storage.Avatars.Open(user.CustomAvatarRelativePath()) - if err != nil { - return err - } - userAvatarImage, _, err := image.Decode(userAvatarFile) - if err != nil { - return err - } - card.DrawImage(userAvatarImage) - } - } else { - posterAvatarLink := user.AvatarLinkWithSize(ctx, 256) - card.DrawExternalImage(posterAvatarLink) - } - return nil -} - // UpdateIssueTitle change issue's title func UpdateIssueTitle(ctx *context.Context) { issue := GetActionIssue(ctx) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 65d526d2f2..ae7a645791 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -365,11 +365,7 @@ func SingleRelease(ctx *context.Context) { addVerifyTagToContext(ctx) ctx.Data["PageIsSingleTag"] = release.IsTag - if release.IsTag { - ctx.Data["Title"] = release.TagName - } else { - ctx.Data["Title"] = release.Title - } + ctx.Data["Title"] = release.DisplayName() err = release.LoadArchiveDownloadCount(ctx) if err != nil { @@ -378,6 +374,12 @@ func SingleRelease(ctx *context.Context) { } ctx.Data["Releases"] = releases + + ctx.Data["OpenGraphTitle"] = fmt.Sprintf("%s - %s", release.DisplayName(), release.Repo.FullName()) + ctx.Data["OpenGraphDescription"] = base.EllipsisString(release.Note, 300) + ctx.Data["OpenGraphURL"] = release.HTMLURL() + ctx.Data["OpenGraphImageURL"] = release.SummaryCardURL() + ctx.HTML(http.StatusOK, tplReleasesList) } diff --git a/routers/web/web.go b/routers/web/web.go index 6061863895..c624ea6e59 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1146,9 +1146,10 @@ func registerRoutes(m *web.Route) { m.Group("/{type:issues|pulls}", func() { m.Group("/{index}", func() { m.Get("/info", repo.GetIssueInfo) - m.Get("/summary-card", repo.GetSummaryCard) + m.Get("/summary-card", repo.DrawIssueSummaryCard) }) }) + m.Get("/-/summary-card", repo.DrawRepoSummaryCard) }, ignSignIn, context.RepoAssignment, context.UnitTypes()) // for "/{username}/{reponame}" which doesn't require authentication // Grouping for those endpoints that do require authentication @@ -1298,6 +1299,7 @@ func registerRoutes(m *web.Route) { m.Get("/latest", repo.LatestRelease) m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS) m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom) + m.Get("/summary-card/{releaseID}", repo.DrawReleaseSummaryCard) }, ctxDataSet("EnableFeed", setting.Other.EnableFeed), repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true)) m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment) diff --git a/services/context/repo.go b/services/context/repo.go index 45a046eff6..de4e33eb1f 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -632,6 +632,8 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID) } + ctx.Data["OpenGraphImageURL"] = repo.SummaryCardURL() + if repo.IsFork { RetrieveBaseRepo(ctx, repo) if ctx.Written() { diff --git a/templates/base/head_opengraph.tmpl b/templates/base/head_opengraph.tmpl index be9829bf97..bcdfa25695 100644 --- a/templates/base/head_opengraph.tmpl +++ b/templates/base/head_opengraph.tmpl @@ -1,4 +1,16 @@ {{- /* og:description - a one to two sentence description of your object, maybe it only needs at most 300 bytes */ -}} +{{if .OpenGraphTitle}} + +{{end}} +{{if .OpenGraphDescription}} + +{{end}} +{{if .OpenGraphURL}} + +{{end}} +{{if .OpenGraphImageURL}} + +{{end}} {{if .PageIsUserProfile}} @@ -35,14 +47,18 @@ {{end}} {{else}} - - - {{if .Repository.Description}} + {{if not .OpenGraphTitle}} + + {{end}} + {{if not .OpenGraphURL}} + + {{end}} + {{if and (.Repository.Description) (not .OpenGraphDescription)}} {{end}} {{end}} - {{if not .Issue}} + {{if and (not .Issue) (not .OpenGraphImageURL)}} {{if (.Repository.AvatarLink ctx)}} {{else}} diff --git a/tests/integration/opengraph_test.go b/tests/integration/opengraph_test.go index 40013bd247..89e2a85acd 100644 --- a/tests/integration/opengraph_test.go +++ b/tests/integration/opengraph_test.go @@ -97,7 +97,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:title": "repo49/test/test.txt at master", "og:url": setting.AppURL + "/user27/repo49/src/branch/master/test/test.txt", "og:type": "object", - "og:image": setting.AppURL + "assets/img/avatar_default.png", + "og:image": setting.AppURL + "user27/repo49/-/summary-card", "og:site_name": siteName, }, }, @@ -108,7 +108,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:title": "Page With Spaced Name", "og:url": setting.AppURL + "/user2/repo1/wiki/Page-With-Spaced-Name", "og:type": "object", - "og:image": setting.AppURL + "avatars/ab53a2911ddf9b4817ac01ddcd3d975f", + "og:image": setting.AppURL + "user2/repo1/-/summary-card", "og:site_name": siteName, }, }, @@ -119,7 +119,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:title": "repo1", "og:url": setting.AppURL + "user2/repo1", "og:type": "object", - "og:image": setting.AppURL + "avatars/ab53a2911ddf9b4817ac01ddcd3d975f", + "og:image": setting.AppURL + "user2/repo1/-/summary-card", "og:site_name": siteName, }, }, @@ -131,7 +131,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:url": setting.AppURL + "user27/repo49", "og:description": "A wonderful repository with more than just a README.md", "og:type": "object", - "og:image": setting.AppURL + "assets/img/avatar_default.png", + "og:image": setting.AppURL + "user27/repo49/-/summary-card", "og:site_name": siteName, }, }, @@ -166,6 +166,10 @@ func TestOpenGraphSummaryCard(t *testing.T) { name string url string }{ + { + name: "repo", + url: "/user2/repo1/-/summary-card", + }, { name: "issue", url: "/user2/repo1/issues/1/summary-card", @@ -174,6 +178,10 @@ func TestOpenGraphSummaryCard(t *testing.T) { name: "pull request", url: "/user2/repo1/pulls/2/summary-card", }, + { + name: "release", + url: "/user2/repo1/releases/summary-card/1", + }, } for _, tc := range cases { From 25bfd2983ef349163b8961cbd6d369fec5841660 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Wed, 18 Dec 2024 20:26:39 +0100 Subject: [PATCH 0005/1052] Update --- models/issues/issue.go | 14 -------------- options/locale/locale_en-US.ini | 2 ++ routers/web/repo/card.go | 31 +++++++++++++++--------------- routers/web/repo/issue.go | 2 ++ routers/web/repo/release.go | 1 + services/context/repo.go | 5 +++++ templates/base/head_opengraph.tmpl | 13 +++++++++---- 7 files changed, 35 insertions(+), 33 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index 17391ffe6c..fbbc4828a2 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -416,20 +416,6 @@ func (issue *Issue) SummaryCardURL() string { return fmt.Sprintf("%s/summary-card", issue.HTMLURL()) } -func (issue *Issue) SummaryCardSize() (int, int) { - return 1200, 600 -} - -func (issue *Issue) SummaryCardWidth() int { - width, _ := issue.SummaryCardSize() - return width -} - -func (issue *Issue) SummaryCardHeight() int { - _, height := issue.SummaryCardSize() - return height -} - // Link returns the issue's relative URL. func (issue *Issue) Link() string { var path string diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3a1639db7e..40ed989e21 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1148,6 +1148,7 @@ blame_prior = View blame prior to this change blame.ignore_revs = Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view. blame.ignore_revs.failed = Failed to ignore revisions in .git-blame-ignore-revs. author_search_tooltip = Shows a maximum of 30 users +summary_card_alt = Summary card of repository %s tree_path_not_found_commit = Path %[1]s doesn't exist in commit %[2]s tree_path_not_found_branch = Path %[1]s doesn't exist in branch %[2]s @@ -2740,6 +2741,7 @@ release.asset_name = Asset name release.asset_external_url = External URL release.add_external_asset = Add external asset release.invalid_external_url = Invalid external URL: "%s" +release.summary_card_alt = Summary card of an release titled "%s" in repository %s branch.name = Branch name branch.already_exists = A branch named "%s" already exists. diff --git a/routers/web/repo/card.go b/routers/web/repo/card.go index 3564d6486c..0afdf98d0a 100644 --- a/routers/web/repo/card.go +++ b/routers/web/repo/card.go @@ -25,7 +25,7 @@ import ( "code.gitea.io/gitea/services/context" ) -// drawUser draws a user avator in a summary card +// drawUser draws a user avatar in a summary card func drawUser(ctx *context.Context, card *card.Card, user *user_model.User) error { if user.UseCustomAvatar { posterAvatarPath := user.CustomAvatarRelativePath() @@ -50,6 +50,7 @@ func drawUser(ctx *context.Context, card *card.Card, user *user_model.User) erro // drawRepoIcon draws the repo icon in a summary card func drawRepoIcon(ctx *context.Context, card *card.Card, repo *repo_model.Repository) error { repoAvatarPath := repo.CustomAvatarRelativePath() + if repoAvatarPath != "" { repoAvatarFile, err := storage.RepoAvatars.Open(repoAvatarPath) if err != nil { @@ -61,21 +62,21 @@ func drawRepoIcon(ctx *context.Context, card *card.Card, repo *repo_model.Reposi } card.DrawImage(repoAvatarImage) return nil - } else { - // If the repo didn't have an avatar, fallback to the repo owner's avatar for the right-hand-side icon - err := repo.LoadOwner(ctx) + } + + // If the repo didn't have an avatar, fallback to the repo owner's avatar for the right-hand-side icon + err := repo.LoadOwner(ctx) + if err != nil { + return err + } + if repo.Owner != nil { + err = drawUser(ctx, card, repo.Owner) if err != nil { return err } - if repo.Owner != nil { - err = drawUser(ctx, card, repo.Owner) - if err != nil { - return err - } - } - - return nil } + + return nil } // hexToColor converts a hex color to a go color @@ -227,7 +228,7 @@ func drawRepoSummaryCard(ctx *context.Context, repo *repo_model.Repository) (*ca } func drawIssueSummaryCard(ctx *context.Context, issue *issue_model.Issue) (*card.Card, error) { - width, height := issue.SummaryCardSize() + width, height := card.DefaultSize() mainCard, err := card.NewCard(width, height) if err != nil { return nil, err @@ -397,7 +398,7 @@ func drawReleaseSummaryCard(ctx *context.Context, release *repo_model.Release) ( return mainCard, nil } -// checkCardCache checks if a card in cahce and serves it +// checkCardCache checks if a card in cache and serves it func checkCardCache(ctx *context.Context, cacheKey string) bool { cache := cache.GetCache() pngData, ok := cache.Get(cacheKey).([]byte) @@ -414,7 +415,7 @@ func checkCardCache(ctx *context.Context, cacheKey string) bool { return false } -// serveCard server a Crad to the user adds it to the cache +// serveCard server a Card to the user adds it to the cache func serveCard(ctx *context.Context, card *card.Card, cacheKey string) { cache := cache.GetCache() diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index b86749ec69..0248485dac 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2055,6 +2055,8 @@ func ViewIssue(ctx *context.Context) { ctx.Data["RefEndName"] = git.RefName(issue.Ref).ShortName() ctx.Data["NewPinAllowed"] = pinAllowed ctx.Data["PinEnabled"] = setting.Repository.Issue.MaxPinned != 0 + ctx.Data["OpenGraphImageURL"] = issue.SummaryCardURL() + ctx.Data["OpenGraphImageAltText"] = ctx.Tr("repo.issues.summary_card_alt", issue.Title, issue.Repo.FullName()) prepareHiddenCommentType(ctx) if ctx.Written() { diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index ae7a645791..1791788743 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -379,6 +379,7 @@ func SingleRelease(ctx *context.Context) { ctx.Data["OpenGraphDescription"] = base.EllipsisString(release.Note, 300) ctx.Data["OpenGraphURL"] = release.HTMLURL() ctx.Data["OpenGraphImageURL"] = release.SummaryCardURL() + ctx.Data["OpenGraphImageAltText"] = ctx.Tr("repo.release.summary_card_alt", release.DisplayName(), release.Repo.FullName()) ctx.HTML(http.StatusOK, tplReleasesList) } diff --git a/services/context/repo.go b/services/context/repo.go index de4e33eb1f..462d843bfc 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -25,6 +25,7 @@ import ( unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/card" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" code_indexer "code.gitea.io/gitea/modules/indexer/code" @@ -632,7 +633,11 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID) } + cardWidth, cardHeight := card.DefaultSize() ctx.Data["OpenGraphImageURL"] = repo.SummaryCardURL() + ctx.Data["OpenGraphImageWidth"] = cardWidth + ctx.Data["OpenGraphImageHeight"] = cardHeight + ctx.Data["OpenGraphImageAltText"] = ctx.Tr("repo.summary_card_alt", repo.FullName()) if repo.IsFork { RetrieveBaseRepo(ctx, repo) diff --git a/templates/base/head_opengraph.tmpl b/templates/base/head_opengraph.tmpl index bcdfa25695..692f1797b6 100644 --- a/templates/base/head_opengraph.tmpl +++ b/templates/base/head_opengraph.tmpl @@ -10,6 +10,15 @@ {{end}} {{if .OpenGraphImageURL}} + {{if .OpenGraphImageWidth}} + + {{end}} + {{if .OpenGraphImageHeight}} + + {{end}} + {{if .OpenGraphImageAltText}} + + {{end}} {{end}} {{if .PageIsUserProfile}} @@ -26,10 +35,6 @@ {{if .Issue.Content}} {{end}} - - - - {{else if or .PageIsDiff .IsViewFile}} From aa64f6515c6cb34a9d70ef891b3d187878a71cd8 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Wed, 18 Dec 2024 20:41:40 +0100 Subject: [PATCH 0006/1052] Add copyright header --- routers/web/repo/card.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/routers/web/repo/card.go b/routers/web/repo/card.go index 0afdf98d0a..23cd705ba5 100644 --- a/routers/web/repo/card.go +++ b/routers/web/repo/card.go @@ -1,3 +1,6 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + package repo import ( From 4568ebc91318a3451da14989eeb321544b95743a Mon Sep 17 00:00:00 2001 From: JakobDev Date: Wed, 18 Dec 2024 22:33:22 +0100 Subject: [PATCH 0007/1052] Update --- models/repo/release.go | 2 +- routers/web/repo/card.go | 2 +- routers/web/web.go | 2 +- tests/integration/opengraph_test.go | 70 ++++++++++++++++++++--------- 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/models/repo/release.go b/models/repo/release.go index 86cb553da0..cd6f322435 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -191,7 +191,7 @@ func (r *Release) Link() string { // SummaryCardURL returns the absolute URL to an image providing a summary of the release func (r *Release) SummaryCardURL() string { - return fmt.Sprintf("%s/releases/summary-card/%d", r.Repo.HTMLURL(), r.ID) + return fmt.Sprintf("%s/releases/summary-card/%s", r.Repo.HTMLURL(), util.PathEscapeSegments(r.TagName)) } // DisplayName retruns the name of the release diff --git a/routers/web/repo/card.go b/routers/web/repo/card.go index 23cd705ba5..1fe7283c8d 100644 --- a/routers/web/repo/card.go +++ b/routers/web/repo/card.go @@ -494,7 +494,7 @@ func DrawIssueSummaryCard(ctx *context.Context) { } func DrawReleaseSummaryCard(ctx *context.Context) { - release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":releaseID")) + release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Params("*")) if err != nil { if repo_model.IsErrReleaseNotExist(err) { ctx.NotFound("", nil) diff --git a/routers/web/web.go b/routers/web/web.go index c624ea6e59..4d8d280c89 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1299,7 +1299,7 @@ func registerRoutes(m *web.Route) { m.Get("/latest", repo.LatestRelease) m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS) m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom) - m.Get("/summary-card/{releaseID}", repo.DrawReleaseSummaryCard) + m.Get("/summary-card/*", repo.DrawReleaseSummaryCard) }, ctxDataSet("EnableFeed", setting.Other.EnableFeed), repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true)) m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment) diff --git a/tests/integration/opengraph_test.go b/tests/integration/opengraph_test.go index 89e2a85acd..d54f59c9b8 100644 --- a/tests/integration/opengraph_test.go +++ b/tests/integration/opengraph_test.go @@ -94,45 +94,71 @@ func TestOpenGraphProperties(t *testing.T) { name: "file in repo", url: "/user27/repo49/src/branch/master/test/test.txt", expected: map[string]string{ - "og:title": "repo49/test/test.txt at master", - "og:url": setting.AppURL + "/user27/repo49/src/branch/master/test/test.txt", - "og:type": "object", - "og:image": setting.AppURL + "user27/repo49/-/summary-card", - "og:site_name": siteName, + "og:title": "repo49/test/test.txt at master", + "og:url": setting.AppURL + "/user27/repo49/src/branch/master/test/test.txt", + "og:type": "object", + "og:image": setting.AppURL + "user27/repo49/-/summary-card", + "og:image:alt": "Summary card of repository user27/repo49", + "og:image:width": "1200", + "og:image:height": "600", + "og:site_name": siteName, }, }, { name: "wiki page for repo without description", url: "/user2/repo1/wiki/Page-With-Spaced-Name", expected: map[string]string{ - "og:title": "Page With Spaced Name", - "og:url": setting.AppURL + "/user2/repo1/wiki/Page-With-Spaced-Name", - "og:type": "object", - "og:image": setting.AppURL + "user2/repo1/-/summary-card", - "og:site_name": siteName, + "og:title": "Page With Spaced Name", + "og:url": setting.AppURL + "/user2/repo1/wiki/Page-With-Spaced-Name", + "og:type": "object", + "og:image": setting.AppURL + "user2/repo1/-/summary-card", + "og:image:alt": "Summary card of repository user2/repo1", + "og:image:width": "1200", + "og:image:height": "600", + "og:site_name": siteName, }, }, { name: "index page for repo without description", url: "/user2/repo1", expected: map[string]string{ - "og:title": "repo1", - "og:url": setting.AppURL + "user2/repo1", - "og:type": "object", - "og:image": setting.AppURL + "user2/repo1/-/summary-card", - "og:site_name": siteName, + "og:title": "repo1", + "og:url": setting.AppURL + "user2/repo1", + "og:type": "object", + "og:image": setting.AppURL + "user2/repo1/-/summary-card", + "og:image:alt": "Summary card of repository user2/repo1", + "og:image:width": "1200", + "og:image:height": "600", + "og:site_name": siteName, }, }, { name: "index page for repo with description", url: "/user27/repo49", expected: map[string]string{ - "og:title": "repo49", - "og:url": setting.AppURL + "user27/repo49", - "og:description": "A wonderful repository with more than just a README.md", - "og:type": "object", - "og:image": setting.AppURL + "user27/repo49/-/summary-card", - "og:site_name": siteName, + "og:title": "repo49", + "og:url": setting.AppURL + "user27/repo49", + "og:description": "A wonderful repository with more than just a README.md", + "og:type": "object", + "og:image": setting.AppURL + "user27/repo49/-/summary-card", + "og:image:alt": "Summary card of repository user27/repo49", + "og:image:width": "1200", + "og:image:height": "600", + "og:site_name": siteName, + }, + }, + { + name: "release", + url: "/user2/repo1/releases/tag/v1.1", + expected: map[string]string{ + "og:title": "testing-release - user2/repo1", + "og:url": setting.AppURL + "user2/repo1/releases/tag/v1.1", + "og:type": "object", + "og:image": setting.AppURL + "user2/repo1/releases/summary-card/v1.1", + "og:image:alt": "Summary card of an release titled \"testing-release\" in repository user2/repo1", + "og:image:width": "1200", + "og:image:height": "600", + "og:site_name": siteName, }, }, } @@ -180,7 +206,7 @@ func TestOpenGraphSummaryCard(t *testing.T) { }, { name: "release", - url: "/user2/repo1/releases/summary-card/1", + url: "/user2/repo1/releases/summary-card/v1.1", }, } From 8bd1c7ff319bc4cec9f0e979edd91e1002c0407d Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Thu, 19 Dec 2024 10:47:47 +0000 Subject: [PATCH 0008/1052] feat: add synchronization for SSH keys in handleOAuth2SignIn --- routers/web/auth/oauth.go | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index fbdd47479a..16c3c9f9d4 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -48,6 +48,8 @@ import ( "github.com/markbates/goth/providers/openidConnect" "github.com/markbates/goth/providers/zoom" go_oauth2 "golang.org/x/oauth2" + + asymkey_model "code.gitea.io/gitea/models/asymkey" ) const ( @@ -1183,8 +1185,62 @@ func updateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) { } } +func getSSHKeys(source *oauth2.Source, gothUser *goth.User) ([]string, error) { + key := source.AttributeSSHPublicKey + value, exists := gothUser.RawData[key] + if !exists { + return nil, fmt.Errorf("attribute '%s' not found in user data", key) + } + + rawSlice, ok := value.([]any) + if !ok { + return nil, fmt.Errorf("unexpected type for SSH public key, expected []interface{} but got %T", value) + } + + sshKeys := make([]string, 0, len(rawSlice)) + for i, v := range rawSlice { + str, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unexpected element type at index %d in SSH public key array, expected string but got %T", i, v) + } + sshKeys = append(sshKeys, str) + } + + return sshKeys, nil +} + +func updateSshPubIfNeed( + ctx *context.Context, + authSource *auth.Source, + fetchedUser *goth.User, + user *user_model.User, +) error { + oauth2Source := authSource.Cfg.(*oauth2.Source) + + if oauth2Source.ProvidesSSHKeys() { + sshKeys, err := getSSHKeys(oauth2Source, fetchedUser) + if err != nil { + return err + } + + if len(sshKeys) == 0 { + return nil + } + + if asymkey_model.SynchronizePublicKeys(ctx, user, authSource, sshKeys) { + err = asymkey_model.RewriteAllPublicKeys(ctx) + if err != nil { + return err + } + } + } + + return nil +} + func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) { updateAvatarIfNeed(ctx, gothUser.AvatarURL, u) + updateSshPubIfNeed(ctx, source, &gothUser, u) needs2FA := false if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA { From 32de0745e438a55bb4fc3cb10c29580af89f4618 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Thu, 19 Dec 2024 11:17:28 +0000 Subject: [PATCH 0009/1052] style: fix fmt and lint --- routers/web/auth/oauth.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 16c3c9f9d4..48aaca17cb 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -17,6 +17,7 @@ import ( "sort" "strings" + asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" org_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -48,8 +49,6 @@ import ( "github.com/markbates/goth/providers/openidConnect" "github.com/markbates/goth/providers/zoom" go_oauth2 "golang.org/x/oauth2" - - asymkey_model "code.gitea.io/gitea/models/asymkey" ) const ( @@ -1209,7 +1208,7 @@ func getSSHKeys(source *oauth2.Source, gothUser *goth.User) ([]string, error) { return sshKeys, nil } -func updateSshPubIfNeed( +func updateSSHPubIfNeed( ctx *context.Context, authSource *auth.Source, fetchedUser *goth.User, @@ -1240,7 +1239,11 @@ func updateSshPubIfNeed( func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) { updateAvatarIfNeed(ctx, gothUser.AvatarURL, u) - updateSshPubIfNeed(ctx, source, &gothUser, u) + err := updateSSHPubIfNeed(ctx, source, &gothUser, u) + if err != nil { + ctx.ServerError("updateSSHPubIfNeed", err) + return + } needs2FA := false if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA { From 515eec3d1a02603c07e2bc39bb351792d4ac95e8 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 21 Dec 2024 21:25:38 +0000 Subject: [PATCH 0010/1052] remove `len(sshKeys) == 0` check --- routers/web/auth/oauth.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 48aaca17cb..4d8fbcb7a5 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1222,10 +1222,6 @@ func updateSSHPubIfNeed( return err } - if len(sshKeys) == 0 { - return nil - } - if asymkey_model.SynchronizePublicKeys(ctx, user, authSource, sshKeys) { err = asymkey_model.RewriteAllPublicKeys(ctx) if err != nil { From 283d883e5ae01eef16e2c9f63929122cd816162e Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 21 Dec 2024 21:25:45 +0000 Subject: [PATCH 0011/1052] add integration tests for synchronization for SSH keys with OpenID Connect --- tests/integration/oauth_test.go | 111 ++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index f385b99e46..675573d6cb 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -691,6 +691,117 @@ func TestSignInOAuthCallbackRedirectToEscaping(t *testing.T) { assert.Equal(t, "/login/oauth/authorize?redirect_uri=https://translate.example.org", test.RedirectURL(resp)) } +func setupMockOIDCServer() *httptest.Server { + var mockServer *httptest.Server + mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/.well-known/openid-configuration": + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{ + "issuer": "` + mockServer.URL + `", + "authorization_endpoint": "` + mockServer.URL + `/authorize", + "token_endpoint": "` + mockServer.URL + `/token", + "userinfo_endpoint": "` + mockServer.URL + `/userinfo" + }`)) + default: + http.NotFound(w, r) + } + })) + return mockServer +} + +func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { + defer tests.PrepareTestEnv(t)() + mockServer := setupMockOIDCServer() + defer mockServer.Close() + + sourceName := "oidc" + authPayload := authSourcePayloadOpenIDConnect(sourceName, mockServer.URL+"/") + authPayload["oauth2_attribute_ssh_public_key"] = "sshpubkey" + authSource := addAuthSource(t, authPayload) + + userID := "5678" + user := &user_model.User{ + Name: "oidc.user", + Email: "oidc.user@example.com", + Passwd: "oidc.userpassword", + Type: user_model.UserTypeIndividual, + LoginType: auth_model.OAuth2, + LoginSource: authSource.ID, + LoginName: userID, + IsActive: true, + } + defer createUser(context.Background(), t, user)() + + for _, tt := range []struct { + name string + rawData map[string]interface{} + parsedKeySets []string + }{ + { + name: "Add keys", + rawData: map[string]interface{}{ + "sshpubkey": []interface{}{ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINDRDoephkaFELacrNNe2fqAwedhRB1MKOpLEHlPuczO nocomment", + }, + }, + parsedKeySets: []string{ + "SHA256:X/mW7JUQ8J8yhrKBbZ/pJni8qx7zPA1DTFsi8ftpDwg", + }, + }, + { + name: "Update keys", + rawData: map[string]interface{}{ + "sshpubkey": []interface{}{ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMLLMOLFMouSJmzOASKKv178d+7op4utSxcugF9tVVch nocomment", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGyDh9sg1IGQGa0U363wcGXrDlGBhZI3UHvS7we/0d+T nocomment", + }, + }, + parsedKeySets: []string{ + "SHA256:gsyG4JNmY5XoLBK5lSzuwD3EXcaDBiDKBkqDkpQTH6Q", + "SHA256:bbEKB1Qpumgk6QrgiN6t/kIvtUZvIQ8rqQBz8yYPzYw", + }, + }, + { + name: "Remove keys", + rawData: map[string]interface{}{ + "sshpubkey": []interface{}{}, + }, + parsedKeySets: []string{}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + return goth.User{ + Provider: sourceName, + UserID: userID, + Email: user.Email, + RawData: tt.rawData, + }, nil + })() + + session := emptyTestSession(t) + + req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", sourceName)) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", test.RedirectURL(resp)) + + req = NewRequest(t, "GET", "/user/settings/keys") + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + divs := htmlDoc.doc.Find("#keys-ssh .flex-item .flex-item-body:not(:last-child)") + + syncedKeys := make([]string, divs.Length()) + for i := 0; i < divs.Length(); i++ { + syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text()) + } + + assert.ElementsMatch(t, tt.parsedKeySets, syncedKeys, "Unequal number of keys") + }) + } +} + func TestSignUpViaOAuthWithMissingFields(t *testing.T) { defer tests.PrepareTestEnv(t)() // enable auto-creation of accounts via OAuth2 From 4f92b738b50da841a757c42bb34c565cde417615 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 21 Dec 2024 21:44:05 +0000 Subject: [PATCH 0012/1052] style: make fmt --- tests/integration/oauth_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index 675573d6cb..6b5a1feb16 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -735,13 +735,13 @@ func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { for _, tt := range []struct { name string - rawData map[string]interface{} + rawData map[string]any parsedKeySets []string }{ { name: "Add keys", - rawData: map[string]interface{}{ - "sshpubkey": []interface{}{ + rawData: map[string]any{ + "sshpubkey": []any{ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINDRDoephkaFELacrNNe2fqAwedhRB1MKOpLEHlPuczO nocomment", }, }, @@ -751,8 +751,8 @@ func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { }, { name: "Update keys", - rawData: map[string]interface{}{ - "sshpubkey": []interface{}{ + rawData: map[string]any{ + "sshpubkey": []any{ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMLLMOLFMouSJmzOASKKv178d+7op4utSxcugF9tVVch nocomment", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGyDh9sg1IGQGa0U363wcGXrDlGBhZI3UHvS7we/0d+T nocomment", }, @@ -764,8 +764,8 @@ func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { }, { name: "Remove keys", - rawData: map[string]interface{}{ - "sshpubkey": []interface{}{}, + rawData: map[string]any{ + "sshpubkey": []any{}, }, parsedKeySets: []string{}, }, From 6836ded397fb7132868fac5a7b1593b932fa46e5 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Wed, 25 Dec 2024 11:21:59 +0100 Subject: [PATCH 0013/1052] chore(release): first commit of v11.0 --- release-notes-published/10.0.0.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 release-notes-published/10.0.0.md diff --git a/release-notes-published/10.0.0.md b/release-notes-published/10.0.0.md new file mode 100644 index 0000000000..e69de29bb2 From 72dcf046900b66d86c1a31022c2b4d62304c6858 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Wed, 25 Dec 2024 12:00:15 +0100 Subject: [PATCH 0014/1052] chore(release): v10.0 is cut, v9.0 is soon to be EOL --- renovate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/renovate.json b/renovate.json index 4e869db579..64e610cd57 100644 --- a/renovate.json +++ b/renovate.json @@ -9,8 +9,8 @@ "baseBranches": [ "$default", "/^v7\\.\\d+/forgejo$/", - "/^v9\\.\\d+/forgejo$/", - "/^v10\\.\\d+/forgejo$/" + "/^v10\\.\\d+/forgejo$/", + "/^v11\\.\\d+/forgejo$/" ], "postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths", "npmDedupe"], "prConcurrentLimit": 10, From dd4a1107ed8977594be48d06aaec2039c983c943 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Mon, 16 Dec 2024 13:44:25 +0100 Subject: [PATCH 0015/1052] template: repo: compare: display a warning if the user is not logged in Signed-off-by: Litchi Pi --- options/locale/locale_en-US.ini | 1 + routers/web/repo/compare.go | 1 + templates/repo/diff/compare.tmpl | 4 ++++ tests/integration/compare_test.go | 40 +++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3b413b8f92..f00d738026 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1845,6 +1845,7 @@ pulls.new = New pull request pulls.view = View pull request pulls.edit.already_changed = Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes pulls.compare_changes = New pull request +pulls.sign_in_require = Sign in to create a new pull request. pulls.allow_edits_from_maintainers = Allow edits from maintainers pulls.allow_edits_from_maintainers_desc = Users with write access to the base branch can also push to this branch pulls.allow_edits_from_maintainers_err = Updating failed diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index e5eab2bffa..03d49fa692 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -51,6 +51,7 @@ const ( func setCompareContext(ctx *context.Context, before, head *git.Commit, headOwner, headName string) { ctx.Data["BeforeCommit"] = before ctx.Data["HeadCommit"] = head + ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + url.QueryEscape(ctx.Data["Link"].(string)) ctx.Data["GetBlobByPathForCommit"] = func(commit *git.Commit, path string) *git.Blob { if commit == nil { diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 612d08ec4f..024577afcc 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -215,6 +215,10 @@ {{ctx.Locale.Tr "repo.archive.title_date" (DateUtils.AbsoluteLong .Repository.ArchivedUnix)}} {{end}}
+ {{else}} +
+ {{ctx.Locale.Tr "repo.pulls.sign_in_require" .SignInLink}} +
{{end}} {{if $.IsSigned}}
diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go index a6f994835b..2d27a430ac 100644 --- a/tests/integration/compare_test.go +++ b/tests/integration/compare_test.go @@ -291,3 +291,43 @@ func TestCompareCodeExpand(t *testing.T) { }) }) } + +func TestCompareSignedIn(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // Setup the test with a connected user + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testCreateBranch(t, session, "user1", "repo1", "branch/master", "recent-push", http.StatusSeeOther) + testEditFile(t, session, "user1", "repo1", "recent-push", "README.md", "Hello recently!\n") + + newPrSelector := "button.ui.button.primary.show-form" + + t.Run("PR creation button displayed if logged in", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user1/repo1/compare/master...recent-push") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + // Check that the "Sign in" button doesn't show up + htmlDoc.AssertElement(t, "a[href='/user/login?redirect_to=%2Fuser1%2Frepo1%2Fcompare%2Fmaster...recent-push']", false) + + // Check that the "New pull request" button shows up + htmlDoc.AssertElement(t, newPrSelector, true) + }) + + t.Run("no PR creation button but display warning", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user1/repo1/compare/master...recent-push") + resp := MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + // Check that the "Sign in" button shows up + htmlDoc.AssertElement(t, "a[href='/user/login?redirect_to=%2Fuser1%2Frepo1%2Fcompare%2Fmaster...recent-push']", true) + + // Check that the "New pull request" button doesn't show up + htmlDoc.AssertElement(t, newPrSelector, false) + }) + }) +} From 64deec434a5910a3515d18c5a135848436cc76a7 Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 25 Dec 2024 23:08:03 +0100 Subject: [PATCH 0016/1052] Revert "Update dependency idiomorph to v0.4.0" This reverts commit f9aaefd107bc6583a348e89fb76f41443224a821. I've not not yet been able to determine what commit caused it, but 0.4.0 is broken for Forgejo's usecase it's not morphing and instead replacing (it seems) elements when there's no need to. --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae637779dd..e081796a52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "escape-goat": "4.0.0", "fast-glob": "3.3.2", "htmx.org": "1.9.12", - "idiomorph": "0.4.0", + "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.18", "mermaid": "11.4.1", @@ -9314,10 +9314,10 @@ } }, "node_modules/idiomorph": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/idiomorph/-/idiomorph-0.4.0.tgz", - "integrity": "sha512-VdXFpZOTXhLatJmhCWJR5oQKLXT01O6sFCJqT0/EqG71C4tYZdPJ5etvttwWsT2WKRYWz160XkNr1DUqXNMZHg==", - "license": "BSD-2-Clause" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/idiomorph/-/idiomorph-0.3.0.tgz", + "integrity": "sha512-UhV1Ey5xCxIwR9B+OgIjQa+1Jx99XQ1vQHUsKBU1RpQzCx1u+b+N6SOXgf5mEJDqemUI/ffccu6+71l2mJUsRA==", + "license": "BSD 2-Clause" }, "node_modules/ieee754": { "version": "1.2.1", diff --git a/package.json b/package.json index c674c011dd..fe7c30471b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "escape-goat": "4.0.0", "fast-glob": "3.3.2", "htmx.org": "1.9.12", - "idiomorph": "0.4.0", + "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.18", "mermaid": "11.4.1", From 5eb08773814513bd688e714c4ee1ebf905c44f55 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Thu, 26 Dec 2024 09:40:21 +0100 Subject: [PATCH 0017/1052] chore(i18n): user/label translations in danish/latvian Refs: https://codeberg.org/forgejo/forgejo/pulls/6331 --- build/lint-locale.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/lint-locale.go b/build/lint-locale.go index c49e236938..44d320994c 100644 --- a/build/lint-locale.go +++ b/build/lint-locale.go @@ -59,9 +59,9 @@ func initRemoveTags() { oldnew := []string{} for _, el := range []string{ "email@example.com", "correu@example.com", "epasts@domens.lv", "email@exemplo.com", "eposta@ornek.com", "email@pรฉlda.hu", "email@esempio.it", - "user", "utente", "lietotฤjs", "gebruiker", "usuรกrio", "Benutzer", "Bruker", + "user", "utente", "lietotฤjs", "gebruiker", "usuรกrio", "Benutzer", "Bruker", "bruger", "server", "servidor", "kiszolgรกlรณ", "serveris", - "label", "etichetta", "etiฤทete", "rรณtulo", "Label", "utilizador", + "label", "etichetta", "etiฤทete", "rรณtulo", "Label", "utilizador", "etiket", "iezฤซme", } { oldnew = append(oldnew, "<"+el+">", "REPLACED-TAG") } From e94134def63ceaf6b46d5a617b11d4be0f5c1f59 Mon Sep 17 00:00:00 2001 From: Codeberg Translate Date: Thu, 26 Dec 2024 09:26:46 +0000 Subject: [PATCH 0018/1052] i18n: update of translations from Codeberg Translate (#6331) Co-authored-by: 0ko <0ko@users.noreply.translate.codeberg.org> Co-authored-by: Fjuro Co-authored-by: tacaly Co-authored-by: tacaly Co-authored-by: artnay Co-authored-by: WithLithum Co-authored-by: Wuzzy Co-authored-by: Atalanttore Co-authored-by: Juno Takano Co-authored-by: emansije Co-authored-by: earl-warren Co-authored-by: Edgarsons Co-authored-by: Benedikt Straub Co-authored-by: SomeTr Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6331 Reviewed-by: Earl Warren Co-authored-by: Codeberg Translate Co-committed-by: Codeberg Translate --- options/locale/locale_cs-CZ.ini | 3 +- options/locale/locale_da.ini | 907 ++++++++++++++++++++++++++++++++ options/locale/locale_de-DE.ini | 16 +- options/locale/locale_fi-FI.ini | 119 ++++- options/locale/locale_fr-FR.ini | 18 +- options/locale/locale_lv-LV.ini | 124 +++-- options/locale/locale_nds.ini | 1 + options/locale/locale_pt-BR.ini | 32 +- options/locale/locale_pt-PT.ini | 3 + options/locale/locale_ru-RU.ini | 83 +-- options/locale/locale_uk-UA.ini | 29 +- options/locale/locale_zh-CN.ini | 2 +- 12 files changed, 1173 insertions(+), 164 deletions(-) create mode 100644 options/locale/locale_da.ini diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index bd983164b2..375c4a710f 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1010,7 +1010,7 @@ remove_account_link=Odstranit propojenรฝ รบฤet remove_account_link_desc=Odstranฤ›nรญm propojenรฉho รบฤtu zruลกรญte jeho pล™รญstup k vaลกemu Forgejo รบฤtu. Pokraฤovat? remove_account_link_success=Propojenรฝ รบฤet byl odstranฤ›n. -hooks.desc=Pล™idat webhooky, kterรฉ budou spouลกtฤ›ny pro vลกechny repozitรกล™e vve vaลกem vlastnictvรญ. +hooks.desc=Pล™idejte webhooky, kterรฉ budou spouลกtฤ›ny pro vลกechny repozitรกล™e ve vaลกem vlastnictvรญ. orgs_none=Nejste ฤlenem ลพรกdnรฉ organizace. repos_none=Nevlastnรญte ลพรกdnรฉ repozitรกล™e. @@ -2849,6 +2849,7 @@ issues.num_reviews_one = %d kontrola issues.num_reviews_few = %d kontrol issues.summary_card_alt = Souhrn problรฉmu s nรกzvem โ€ž%sโ€œ v repozitรกล™i %s editor.add_tmpl.filename = nazevsouboru +settings.default_update_style_desc = Vรฝchozรญ zpลฏsob aktualizacรญ pouลพรญvanรฝ pro aktualizace ลพรกdostรญ o slouฤenรญ, kterรฉ jsou pozadu oproti zรกkladnรญ vฤ›tvi. [graphs] component_loading_info = Tohle mลฏลพe chvรญli trvatโ€ฆ diff --git a/options/locale/locale_da.ini b/options/locale/locale_da.ini new file mode 100644 index 0000000000..25a3010e05 --- /dev/null +++ b/options/locale/locale_da.ini @@ -0,0 +1,907 @@ + + + +[common] +home = Hjem +dashboard = Instrumentpanel +explore = Udforsk +help = Hjรฆlp +logo = Logo +sign_in = Login +sign_in_with_provider = Login med %s +sign_in_or = eller +sign_out = Logud +sign_up = Register +return_to_forgejo = Vend tilbage til Forgejo +new_repo.title = Ny repository +retry = Prรธv igen +link_account = Link konto +register = Register +powered_by = Baseret pรฅ %s +version = Version +create_new = Opretโ€ฆ +user_profile_and_more = Profil og indstillingerโ€ฆ +page = Side +template = Skabelon +language = Sprog +notifications = Notifikationer +active_stopwatch = Aktiv tidsregistrering +enable_javascript = Dette websted krรฆver JavaScript. +toc = Indholdsfortegnelse +licenses = Licenser +more_items = Flere genstande +username = Brugernavn +email = E-mailadresse +password = Adgangskode +access_token = Adgangstoken +re_type = Bekrรฆft adgangskode +captcha = CAPTCHA +twofa = To-faktor autentificering +twofa_scratch = To-faktor skrabekode +webauthn_sign_in = Tryk pรฅ knappen pรฅ din sikkerhedsnรธgle. Hvis din sikkerhedsnรธgle ikke har nogen knap, skal du indsรฆtte den igen. +webauthn_use_twofa = Brug en tofaktorkode fra din telefon +webauthn_error = Kunne ikke lรฆse din sikkerhedsnรธgle. +webauthn_unsupported_browser = Din browser understรธtter i รธjeblikket ikke WebAuthn. +webauthn_error_unknown = Der opstod en ukendt fejl. Prรธv venligst igen. +webauthn_error_unable_to_process = Serveren kunne ikke behandle din anmodning. +webauthn_error_duplicated = Sikkerhedsnรธglen er ikke tilladt for denne anmodning. Sรธrg for, at nรธglen ikke allerede er registreret. +webauthn_error_empty = Du skal angive et navn for denne nรธgle. +organization = Organisation +mirror = Mirror +new_mirror = Ny mirror +new_fork = Ny repository fork +new_project = Nyt projekt +new_project_column = Ny kolonne +admin_panel = Side administration +settings = Indstillinger +your_profile = Profil +your_starred = Stjernemarkeret +your_settings = Indstillinger +passcode = Passcode +repository = Repository +new_org.title = Ny organisation +new_repo.link = Nyt repository +new_migrate.link = Ny migration +new_org.link = Ny organisation +all = Alle +sources = Kilder +mirrors = Mirrors +collaborative = Samarbejdende +forks = Forks +activities = Aktiviteter +pull_requests = Pull anmodninger +issues = Problemer +milestones = Milepรฆle +ok = OK +cancel = Annuller +rerun = Kรธr igen +rerun_all = Kรธr alle jobs igen +save = Gem +add = Tilfรธj +add_all = Tilfรธj alle +remove = Slet +remove_all = Slet alle +remove_label_str = Slet genstand "%s" +edit = Redigere +view = Se +test = Test +disabled = Deaktiveret +copy = Kopiรฉr +copy_generic = Kopiรฉr til udklipsholder +copy_url = Kopiรฉr URL +copy_path = Kopiรฉr sti +copy_content = Kopier indhold +copy_branch = Kopiรฉr branch navn +copy_success = Kopieret! +copy_error = Kopiering mislykkedes +write = Skriv +preview = Forhรฅndsvisning +loading = Indlรฆserโ€ฆ +error = Fejl +error413 = Du har opbrugt din kvote. +go_back = Gรฅ tilbage +never = Aldrig +unknown = Ukendt +rss_feed = RSS feed +pin = Pin +unpin = Frigรธr +artifacts = Artefakter +archived = Arkiveret +concept_system_global = Global +concept_user_individual = Individuel +concept_code_repository = Repository +concept_user_organization = Organisation +show_timestamps = Vis tidsstempler +show_log_seconds = Vis sekunder +tracked_time_summary = Opsummering af sporet tid baseret pรฅ filtre af problemliste +signed_in_as = Logget ind som +webauthn_error_insecure = WebAuthn understรธtter kun sikre forbindelser. Til test over HTTP kan du bruge oprindelsen "localhost" eller "127.0.0.1" +invalid_data = Ugyldige data: %v +webauthn_insert_key = Indsรฆt din sikkerhedsnรธgle +webauthn_press_button = Tryk venligst pรฅ knappen pรฅ din sikkerhedsnรธgleโ€ฆ +webauthn_error_timeout = Timeout nรฅet, fรธr din nรธgle kunne lรฆses. Genindlรฆs denne side og prรธv igen. +enabled = Aktiveret +locked = Lรฅst +copy_hash = Kopiรฉr hash +error404 = Den side, du forsรธger at nรฅ, enten findes ikke eller du er ikke autoriseret til at se den. +confirm_delete_artifact = Er du sikker pรฅ, at du vil slette artefakten "%s"? +new_migrate.title = Ny migration +copy_type_unsupported = Denne filtype kan ikke kopieres +toggle_menu = TIl/Fra menu +show_full_screen = Vis fuld skรฆrm +download_logs = Download logs +confirm_delete_selected = Bekrรฆft at slette alle valgte genstande? +name = Navn +value = Vรฆrdi +filter = Filter +filter.clear = Ryd filtre +filter.is_archived = Arkiveret +filter.not_archived = Ikke arkiveret +filter.is_fork = Forks +filter.not_fork = Ikke forks +filter.is_mirror = Mirrors +filter.not_mirror = Ikke mirrors +filter.is_template = Skabeloner +filter.not_template = Ikke skabeloner +filter.public = Offentlig +filter.private = Privat + +[search] +search = Sรธg... +type_tooltip = Sรธge type +fuzzy = Fuzzy +fuzzy_tooltip = Medtag resultater, der ogsรฅ matcher sรธgeordet tรฆt +union = Almindelig +union_tooltip = Inkluder resultater, der matcher et hvilket som helst af de mellemrumsadskilte sรธgeord +exact = Nรธjagtig +exact_tooltip = Medtag kun resultater, der matcher den nรธjagtige sรธgeterm +regexp = RegExp +regexp_tooltip = Fortolk sรธgetermen som et regulรฆrt udtryk +org_kind = Sรธg i organisationer... +team_kind = Sรธg efter hold... +code_kind = Sรธg kode... +code_search_by_git_grep = Aktuelle kodesรธgeresultater leveres af "git grep". Der kan vรฆre bedre resultater, hvis webstedsadministratoren aktiverer kodeindeksering. +package_kind = Sรธg pakker... +project_kind = Sรธg efter projekter... +commit_kind = Sรธg commits... +branch_kind = Sรธg branches... +runner_kind = Sรธg runners... +issue_kind = Sรธg i problemer... +milestone_kind = Sรธg milepรฆle... +pull_kind = Sรธg pulls... +repo_kind = Sรธg depoter... +code_search_unavailable = Kodesรธgning er ikke tilgรฆngelig i รธjeblikket. Kontakt venligst webstedets administrator. +no_results = Ingen matchende resultater fundet. +user_kind = Sรธg brugere... +keyword_search_unavailable = Sรธgning efter nรธgleord er ikke tilgรฆngelig i รธjeblikket. Kontakt venligst webstedets administrator. + +[aria] +navbar = Navigationslinje +footer = Sidefod +footer.software = Omkring dette software +footer.links = Links + +[heatmap] +number_of_contributions_in_the_last_12_months = %s bidrag inden for de sidste 12 mรฅneder +contributions_zero = Ingen bidrag +contributions_format = {contributions} pรฅ {month} {day}, {year} +contributions_one = bidrag +contributions_few = bidragene +less = Mindre +more = Mere + +[editor] +buttons.heading.tooltip = Tilfรธj overskrift +buttons.bold.tooltip = Tilfรธj fed tekst +buttons.italic.tooltip = Tilfรธj kursiv tekst +buttons.quote.tooltip = Citat tekst +buttons.code.tooltip = Tilfรธj kode +buttons.link.tooltip = Tilfรธj et link +buttons.list.unordered.tooltip = Tilfรธj en punktliste +buttons.list.task.tooltip = Tilfรธj en liste over opgaver +buttons.list.ordered.tooltip = Tilfรธj en nummereret liste +buttons.mention.tooltip = Nรฆvn en bruger eller et hold +buttons.ref.tooltip = Henvis til et problem eller pull-anmodning +buttons.enable_monospace_font = Aktiver monospace-skrifttype +buttons.disable_monospace_font = Deaktiver monospace-skrifttype +buttons.new_table.tooltip = Tilfรธj tabel +table_modal.header = Tilfรธj tabel +table_modal.placeholder.header = Hoved +table_modal.placeholder.content = Indhold +table_modal.label.rows = Rรฆkker +table_modal.label.columns = Kolonner +buttons.unindent.tooltip = Udsortere genstande med รฉt niveau +buttons.indent.tooltip = Indlejring af genstande med รฉt niveau +buttons.switch_to_legacy.tooltip = Brug den gamle editor i stedet + +[filter] +string.asc = A - Z +string.desc = Z - A + +[error] +occurred = Der opstod en fejl +not_found = Mรฅlet kunne ikke findes. +network_error = Netvรฆrksfejl +server_internal = Intern serverfejl +report_message = Hvis du mener, at dette er en Forgejo-fejl, skal du sรธge efter problemer pรฅ Codeberg eller รฅbne et nyt problem, hvis det er nรธdvendigt. + +[startpage] +app_desc = En smertefri, selv-hostet Git-tjeneste +install = Nem at installere +platform = Pรฅ tvรฆrs af platforme +platform_desc = Det er bekrรฆftet, at Forgejo kรธrer pรฅ frie operativsystemer som Linux og FreeBSD, samt forskellige CPU-arkitekturer. Vรฆlg den du elsker! +lightweight = Letvรฆgtig +lightweight_desc = Forgejo har lave minimale krav og kan kรธre pรฅ en billig Raspberry Pi. Spar din maskines energi! +license = ร…ben kildekode +license_desc = Fรฅ Forgejo! Slut dig til os ved at bidrage til at gรธre dette projekt endnu bedre. Vรฆr ikke genert over at vรฆre en bidragyder! +install_desc = Du skal blot kรธre binรฆren for din platform, send den med Docker, eller fรฅ det pakket. + +[install] +install = Installation +title = Indledende konfiguration +docker_helper = Hvis du kรธrer Forgejo inde i Docker, skal du lรฆse dokumentationen, fรธr du รฆndrer nogen indstillinger. +require_db_desc = Forgejo krรฆver MySQL, PostgreSQL, SQLite3 eller TiDB (MySQL-protokol). +db_title = Database indstillinger +db_type = Database type +host = Vรฆrt +user = Brugernavn +password = Adgangskode +db_name = Database navn +db_schema = Schema +db_schema_helper = Lad stรฅ tom for databasestandard ("offentlig"). +ssl_mode = SSL +reinstall_error = Du forsรธger at installere i en eksisterende Forgejo-database +reinstall_confirm_check_1 = Dataene krypteret af SECRET_KEY i app.ini kan gรฅ tabt: brugere kan muligvis ikke logge ind med 2FA/OTP, og mirrors fungerer muligvis ikke korrekt. Ved at markere dette felt bekrรฆfter du, at den aktuelle app.ini-fil indeholder den korrekte SECRET_KEY. +reinstall_confirm_check_3 = Du bekrรฆfter, at du er helt sikker pรฅ, at denne Forgejo kรธrer med den korrekte app.ini placering, og at du er sikker pรฅ, at du skal gen-installere. Du bekrรฆfter, at du anerkender ovenstรฅende risici. +err_empty_db_path = SQLite3-databasestien mรฅ ikke vรฆre tom. +no_admin_and_disable_registration = Du kan ikke deaktivere brugerens selvregistrering uden at oprette en administratorkonto. +err_empty_admin_password = Administratoradgangskoden mรฅ ikke vรฆre tom. +err_empty_admin_email = Administrator-e-mailen mรฅ ikke vรฆre tom. +err_admin_name_is_reserved = Administratorbrugernavnet er ugyldigt, brugernavnet er reserveret +err_admin_name_pattern_not_allowed = Administratorbrugernavnet er ugyldigt, brugernavnet matcher et reserveret mรธnster +err_admin_name_is_invalid = Administratorbrugernavnet er ugyldigt +general_title = Generelle indstillinger +app_name = Instans titel +app_slogan_helper = Indtast dit instans slogan her. Lad vรฆre tom for at deaktivere. +lfs_path_helper = Filer sporet af Git LFS vil blive gemt i denne mappe. Lad vรฆre tom for at deaktivere. +run_user = Bruger at kรธre som +lfs_path = Git LFS rodsti +repo_path = Depot rodsti +domain = Server domรฆne +domain_helper = Domรฆne eller vรฆrtsadresse for serveren. +ssh_port = SSH server port +ssh_port_helper = Portnummer, der vil blive brugt af SSH-serveren. Lad vรฆre tomt for at deaktivere SSH-serveren. +http_port = HTTP lytte port +http_port_helper = Portnummer, der vil blive brugt af Forgejo-webserveren. +app_url = Base URL +log_root_path = Log sti +log_root_path_helper = Logfiler vil blive skrevet til denne mappe. +optional_title = Valgfrie indstillinger +email_title = E-mail-indstillinger +smtp_addr = SMTP vรฆrt +smtp_port = SMTP port +smtp_from = Send e-mail som +smtp_from_invalid = "Send e-mail som"-adressen er ugyldig +mailer_user = SMTP brugernavn +mailer_password = SMTP adgangskode +register_confirm = Krรฆv e-mail-bekrรฆftelse for at registrere +mail_notify = Aktiver e-mailmeddelelser +server_service_title = Server- og tredjeparts tjenesteindstillinger +offline_mode = Aktiver lokal tilstand +disable_gravatar = Deaktiver Gravatar +disable_gravatar.description = Deaktiver brug af Gravatar eller andre tredjeparts avatar kilder. Standardbilleder vil blive brugt til bruger avatarer, medmindre de uploader deres egen avatar til instansen. +federated_avatar_lookup = Aktiver fรธdererede avatarer +federated_avatar_lookup.description = Slรฅ avatarer op ved hjรฆlp af Libravatar. +disable_registration = Deaktiver selvregistrering +allow_only_external_registration = Tillad kun registrering via eksterne tjenester +allow_only_external_registration.description = Brugere vil kun vรฆre i stand til at oprette nye konti ved at bruge konfigurerede eksterne tjenester. +openid_signin = Aktiver OpenID-logon +openid_signin.description = Tillad brugere at logge ind via OpenID. +app_slogan = Instans slogan +repo_path_helper = Fjerne Git depoter vil blive gemt i denne mappe. +smtp_from_helper = E-mailadresse Forgejo vil bruge. Indtast en almindelig e-mailadresse, eller brug formatet "Navn" . +run_user_helper = Operativsystemets brugernavn, som Forgejo kรธrer som. Bemรฆrk, at denne bruger skal have adgang til depotets rodsti. +app_name_helper = Indtast dit instans-navn her. Det vil blive vist pรฅ hver side. +offline_mode.description = Deaktiver tredjeparts indholdsleverings netvรฆrk og server alle ressourcer lokalt. +disable_registration.description = Kun instans administratoren vil vรฆre i stand til at oprette nye brugerkonti. Det anbefales stรฆrkt at holde registreringen deaktiveret, medmindre du har til hensigt at vรฆre vรฆrt for en offentlig instans for alle og klar til at hรฅndtere store mรฆngder spamkonti. +openid_signup = Aktiver OpenID-selvregistrering +sqlite_helper = Filsti til SQLite3-databasen.
Indtast en absolut sti, hvis du kรธrer Forgejo som en tjeneste. +path = Sti +reinstall_confirm_message = Geninstallation med en eksisterende Forgejo-database kan forรฅrsage flere problemer. I de fleste tilfรฆlde bรธr du bruge din eksisterende "app.ini" til at kรธre Forgejo. Hvis du ved, hvad du laver, skal du bekrรฆfte fรธlgende: +reinstall_confirm_check_2 = Depoterne og indstillingerne skal muligvis synkroniseres igen. Ved at markere dette felt bekrรฆfter du, at du vil re-synkronisere hooks for depoter og authorized_keys-filen manuelt. Du bekrรฆfter, at du vil sikre dig, at indstillingerne for depoter og mirror er korrekte. +app_url_helper = Base adresse for HTTP(S)-klone-URL'er og e-mail-meddelelser. +enable_captcha = Aktiver registrering CAPTCHA +enable_captcha.description = Krรฆv brugere at bestรฅ CAPTCHA for at oprette konti. +require_sign_in_view = Krรฆv at logge ind for at se instansindhold +default_keep_email_private = Skjul e-mailadresser som standard +default_keep_email_private.description = Aktiver skjulning af e-mail-adresser for nye brugere som standard, sรฅ disse oplysninger ikke lรฆkkes umiddelbart efter tilmelding. +default_allow_create_organization = Tillad oprettelse af organisationer som standard +default_enable_timetracking = Aktiver tidsregistrering som standard +default_enable_timetracking.description = Tillad som standard brug af tidssporings-funktion for nye depoter. +admin_title = Indstillinger for administratorkonto +admin_setting.description = Det er valgfrit at oprette en administratorkonto. Den fรธrste registrerede bruger bliver automatisk administrator. +admin_name = Administrator brugernavn +admin_password = Adgangskode +confirm_password = Bekrรฆft adgangskode +admin_email = E-mailadresse +config_location_hint = Disse konfigurationsmuligheder vil blive gemt i: +install_btn_confirm = Installer Forgejo +invalid_db_setting = Databaseindstillingerne er ugyldige: %v +invalid_db_table = Databasetabellen "%s" er ugyldig: %v +invalid_repo_path = Depotets rodsti er ugyldig: %v +invalid_app_data_path = Appens datasti er ugyldig: %v +run_user_not_match = Brugernavnet som "bruger det skal kรธres som" er ikke det aktuelle brugernavn: %s -> %s +internal_token_failed = Kunne ikke generere intern token: %v +secret_key_failed = Kunne ikke generere hemmelig nรธgle: %v +save_config_failed = Konfigurationen kunne ikke gemmes: %v +invalid_admin_setting = Administratorkonto indstillingen er ugyldig: %v +invalid_log_root_path = Log stien er ugyldig: %v +allow_dots_in_usernames = Tillad brugere at bruge prikker i deres brugernavne. Pรฅvirker ikke eksisterende konti. +no_reply_address = Skjult e-mail-domรฆne +invalid_password_algorithm = Ugyldig hash-algoritme for adgangskode +enable_update_checker = Aktiver opdateringskontrol +env_config_keys = Miljรธkonfiguration +env_config_keys_prompt = Fรธlgende miljรธvariabler vil ogsรฅ blive anvendt pรฅ din konfigurationsfil: +test_git_failed = Kunne ikke teste "git" kommandoen: %v +sqlite3_not_available = Denne Forgejo-version understรธtter ikke SQLite3. Download venligst den officielle binรฆre version fra %s (ikke "gobuild"-versionen). +no_reply_address_helper = Domรฆnenavn til brugere med en skjult e-mailadresse. For eksempel vil brugernavnet "joe" blive logget i Git som "joe@noreply.example.org", hvis det skjulte e-mail-domรฆne er sat til "noreply.example.org". +require_sign_in_view.description = Begrรฆns indholdsadgang til ind-loggede brugere. Gรฆster vil kun kunne besรธge autentificerings sider.. +default_allow_create_organization.description = Tillad nye brugere at oprette organisationer som standard. Nรฅr denne mulighed er deaktiveret, skal en administrator give tilladelse til at oprette organisationer til nye brugere. +password_algorithm = Adgangskode hash algoritme +enable_update_checker_helper_forgejo = Den vil med jรฆvne mellemrum tjekke for nye Forgejo versioner ved at tjekke en TXT DNS-posten pรฅ release.forgejo.org. +password_algorithm_helper = Indstil adgangskode-hash-algoritmen. Algoritmer har forskellige krav og styrke. Argon2-algoritmen er ret sikker, men bruger meget hukommelse og kan vรฆre upassende til smรฅ systemer. +openid_signup.description = Tillad brugere at oprette konti via OpenID, hvis selvregistrering er aktiveret. + +[home] +uname_holder = Brugernavn eller e-mailadresse +switch_dashboard_context = Skift instrumentpanel-kontekst +my_repos = Depoter +my_orgs = Organisationer +view_home = Se %s +filter = Andre filtre +filter_by_team_repositories = Filtrer efter holddepoter +feed_of = Feed of "%s" +show_archived = Arkiveret +show_both_archived_unarchived = Viser bรฅde arkiveret og ikke-arkiveret +show_only_archived = Viser kun arkiverede +show_only_unarchived = Viser kun ikke-arkiveret +show_private = Privat +show_only_private = Viser kun privat +show_only_public = Viser kun offentligt +issues.in_your_repos = I dine depoter +show_both_private_public = Viser bรฅde offentlige og private + +[explore] +repos = Depoter +users = Brugere +stars_one = %d stjerne +stars_few = %d stjerner +forks_one = %d fork +forks_few = %d forks +organizations = Organisationer +code = Kode +code_last_indexed_at = Sidst indekseret %s +relevant_repositories = Kun relevante depoter vises, vis ufiltrerede resultater. +go_to = Gรฅ til +relevant_repositories_tooltip = Depoter som er forks, eller som ikke har noget emne, intet ikon og ingen beskrivelse, er skjult. + +[auth] +create_new_account = Registrer konto +disable_register_prompt = Registrering er deaktiveret. Kontakt venligst din side-administrator. +disable_register_mail = E-mailbekrรฆftelse for registrering er deaktiveret. +manual_activation_only = Kontakt din side-administrator for at fuldfรธre aktiveringen. +remember_me = Husk denne enhed +forgot_password_title = Glemt adgangskode +forgot_password = Glemt adgangskode? +hint_register = Har du brug for en konto? Registrer dig nu. +sign_up_button = Registrer nu. +sign_up_successful = Kontoen blev oprettet. Velkommen! +must_change_password = Opdater din adgangskode +allow_password_change = Krรฆv, at brugeren รฆndrer adgangskode (anbefales) +reset_password_mail_sent_prompt = En bekrรฆftelses-e-mail er blevet sendt til %s. For at fuldfรธre kontogendannelses-processen skal du tjekke din indbakke og fรธlge det medfรธlgende link inden for de nรฆste %s. +active_your_account = Aktiver din konto +account_activated = Kontoen er blevet aktiveret +prohibit_login = Kontoen er suspenderet +prohibit_login_desc = Din konto er blevet suspenderet fra interaktion med instansen. Kontakt instans-administratoren for at fรฅ adgang igen. +change_unconfirmed_email_summary = Skift den e-mailadresse, aktiveringsmail sendes til. +change_unconfirmed_email_error = Kan ikke รฆndre e-mailadressen: %v +resend_mail = Klik her for at sende din aktiverings-e-mail igen +send_reset_mail = Send gendannelses-e-mail +reset_password = Kontogendannelse +invalid_code = Din bekrรฆftelseskode er ugyldig eller er udlรธbet. +invalid_code_forgot_password = Din bekrรฆftelseskode er ugyldig eller er udlรธbet. Klik her for at starte en ny session. +invalid_password = Din adgangskode stemmer ikke overens med den adgangskode, der blev brugt til at oprette kontoen. +reset_password_helper = Gendan konto +resent_limit_prompt = Du har allerede anmodet om en aktiverings-e-mail for nylig. Vent venligst 3 minutter, og prรธv igen. +reset_password_wrong_user = Du er logget ind som %s, men linket til kontogendannelse er beregnet til %s +confirmation_mail_sent_prompt = En ny bekrรฆftelses-e-mail er blevet sendt til %s. For at fuldfรธre registreringsprocessen skal du tjekke din indbakke og fรธlge det medfรธlgende link inden for de nรฆste %s. Hvis e-mailen er forkert, kan du logge ind og anmode om, at endnu en bekrรฆftelses-e-mail sendes til en anden adresse. +change_unconfirmed_email = Hvis du har opgivet den forkerte e-mailadresse under tilmeldingen, kan du รฆndre den nedenfor, og der vil i stedet blive sendt en bekrรฆftelse til den nye adresse. +hint_login = Har du allerede en konto? Log ind nu! +has_unconfirmed_mail = Hej %s, du har en ubekrรฆftet e-mailadresse (%s). Hvis du ikke har modtaget en bekrรฆftelses-e-mail eller har brug for at sende en ny, bedes du klikke pรฅ knappen nedenfor. +password_too_short = Adgangskodelรฆngden mรฅ ikke vรฆre mindre end %d tegn. +non_local_account = Ikke-lokale brugere kan ikke opdatere deres adgangskode via Forgejo-webgrรฆnsefladen. +verify = Verificere +unauthorized_credentials = Legitimationsoplysningerne er forkerte eller er udlรธbet. Prรธv din kommando igen, eller se %s for at fรฅ flere oplysninger +scratch_code = Skrabekode +use_scratch_code = Brug en skrabekode +use_onetime_code = Brug en engangskode +twofa_scratch_used = Du har brugt din skrabekode. Du er blevet omdirigeret til siden med to-faktorindstillinger, sรฅ du kan fjerne din enhedstilmelding eller generere en ny skrabekode. +twofa_passcode_incorrect = Din passcode er forkert. Hvis du har forlagt din enhed, skal du bruge din skrabekode til at logge ind. +twofa_scratch_token_incorrect = Din skrabekode er forkert. +login_userpass = Login +oauth_signup_tab = Registrer ny konto +oauth_signup_title = Fuldfรธr ny konto +oauth_signup_submit = Gennemfรธr konto +oauth_signin_tab = Link til en eksisterende konto +oauth_signin_title = Log ind for at godkende linket konto +oauth_signin_submit = Link konto +oauth.signin.error = Der opstod en fejl under behandling af godkendelsesanmodningen. Hvis denne fejl fortsรฆtter, bedes du kontakte webstedets administrator. +oauth.signin.error.access_denied = Godkendelsesanmodningen blev afvist. +oauth.signin.error.temporarily_unavailable = Godkendelse mislykkedes, fordi godkendelsesserveren midlertidigt ikke er tilgรฆngelig. Prรธv venligst igen senere. +openid_connect_submit = Forbind +openid_connect_title = Opret forbindelse til en eksisterende konto +openid_register_title = Opret ny konto +openid_register_desc = Den valgte OpenID URI er ukendt. Knyt den til en ny konto her. +disable_forgot_password_mail = Kontogendannelse er deaktiveret, fordi der ikke er konfigureret nogen e-mail. Kontakt venligst din webstedsadministrator. +email_domain_blacklisted = Du kan ikke registrere dig med din e-mailadresse. +authorize_application = Godkend Applikation +authorize_redirect_notice = Du vil blive omdirigeret til %s, hvis du godkender denne applikation. +authorize_application_created_by = Denne applikation blev oprettet af %s. +authorize_application_description = Hvis du giver adgangen, vil den vรฆre i stand til at fรฅ adgang til og skrive til alle dine kontooplysninger, inklusive private depoter og organisationer. +authorize_title = Tillad "%s" at fรฅ adgang til din konto? +authorization_failed = Godkendelse mislykkedes +password_pwned_err = Kunne ikke fuldfรธre anmodningen til HaveIBeenPwned +last_admin = Du kan ikke fjerne den sidste admin. Der skal vรฆre mindst รฉn administrator. +back_to_sign_in = Tilbage til Log ind +sign_in_openid = Fortsรฆt med OpenID +openid_signin_desc = Indtast din OpenID URI. For eksempel: alice.openid.example.org eller https://openid.example.org/alice. +disable_forgot_password_mail_admin = Kontogendannelse er kun tilgรฆngelig, nรฅr e-mail er konfigureret. Konfigurer venligst e-mail for at aktivere kontogendannelse. +password_pwned = Den adgangskode, du valgte, er pรฅ en liste over stjรฅlne adgangskoder, der tidligere er blevet afslรธret i forbindelse med offentlige databrud. Prรธv venligst igen med en anden adgangskode, og overvej ogsรฅ at รฆndre denne adgangskode et andet sted. +openid_connect_desc = Den valgte OpenID URI er ukendt. Knyt den til en ny konto her. +authorization_failed_desc = Godkendelsen mislykkedes, fordi vi har registreret en ugyldig anmodning. Kontakt venligst vedligeholderen af den app, du har forsรธgt at godkende. + +[mail] +view_it_on = Se det pรฅ %s +reply = eller svar direkte pรฅ denne e-mail +link_not_working_do_paste = Virker linket ikke? Prรธv at kopiere og indsรฆtte det i din browsers URL-linje. +hi_user_x = Hej %s, +activate_account = Aktiver venligst din konto +activate_account.text_1 = Hej %[1]s, tak, fordi du registrerede dig hos %[2]s! +activate_account.text_2 = Klik venligst pรฅ fรธlgende link for at aktivere din konto inden for %s: +activate_email = Bekrรฆft din e-mailadresse +admin.new_user.subject = Ny bruger %s har lige tilmeldt sig +admin.new_user.user_info = Brugeroplysninger +admin.new_user.text = Venligst klik her for at administrere denne bruger fra administrationspanelet. +register_notify = Velkommen til %s +register_notify.text_1 = dette er din registreringsbekrรฆftelses-e-mail for %s! +register_notify.text_3 = Hvis en anden har lavet denne konto for dig, skal du fรธrst indstille din adgangskode. +reset_password = Gendan din konto +password_change.subject = Din adgangskode er blevet รฆndret +password_change.text_1 = Adgangskoden til din konto er lige blevet รฆndret. +primary_mail_change.subject = Din primรฆre mail er blevet รฆndret +totp_disabled.subject = TOTP er blevet deaktiveret +totp_disabled.text_1 = Tidsbaseret engangsadgangskode (TOTP) pรฅ din konto er netop blevet deaktiveret. +totp_disabled.no_2fa = Der er ikke lรฆngere konfigureret andre 2FA-metoder, hvilket betyder, at det ikke lรฆngere er nรธdvendigt at logge ind pรฅ din konto hos 2FA. +removed_security_key.subject = En sikkerhedsnรธgle er blevet fjernet +removed_security_key.text_1 = Sikkerhedsnรธglen "%[1]s" er lige blevet fjernet fra din konto. +account_security_caution.text_1 = Hvis dette var dig, sรฅ kan du roligt ignorere denne mail. +totp_enrolled.subject = Du har aktiveret TOTP som 2FA-metode +totp_enrolled.text_1.has_webauthn = Du har lige aktiveret TOTP for din konto. Dette betyder, at du for alle fremtidige login til din konto kan bruge TOTP som en 2FA-metode eller bruge en af dine sikkerhedsnรธgler. +register_success = Registreringen lykkedes +issue_assigned.pull = @%[1]s har tildelt dig at trรฆkke anmodning %[2]s i depotet %[3]s. +issue_assigned.issue = @%[1]s har tildelt dig et problem %[2]s i depotet %[3]s. +register_notify.text_2 = Du kan logge ind pรฅ din konto med dit brugernavn: %s +primary_mail_change.text_1 = Din kontos primรฆre mail er lige blevet รฆndret til %[1]s. Det betyder, at denne e-mailadresse ikke lรฆngere vil modtage e-mail-meddelelser for din konto. +account_security_caution.text_2 = Hvis dette ikke var dig, er din konto kompromitteret. Kontakt venligst administratorerne af dette websted. +activate_email.text = Klik venligst pรฅ fรธlgende link for at bekrรฆfte din e-mailadresse inden for %s: +reset_password.text = Hvis dette var dig, skal du klikke pรฅ fรธlgende link for at gendanne din konto inden for %s: +removed_security_key.no_2fa = Der er ikke lรฆngere konfigureret andre 2FA-metoder, hvilket betyder, at det ikke lรฆngere er nรธdvendigt at logge ind pรฅ din konto hos 2FA. +totp_enrolled.text_1.no_webauthn = Du har lige aktiveret TOTP for din konto. Det betyder, at du for alle fremtidige login til din konto skal bruge TOTP som 2FA-metode. +issue.x_mentioned_you = @%s nรฆvnte dig: +issue.action.force_push = %[1]s tvangs pushed %[2]s fra %[3]s til %[4]s. +issue.action.push_1 = @%[1]s pushed %[3]d commit til %[2]s +issue.action.push_n = @%[1]s pushed %[3]d commits til %[2]s +issue.action.close = @%[1]s lukket #%[2]d. +issue.action.reopen = @%[1]s genรฅbnet #%[2]d. +issue.action.merge = @%[1]s merged #%[2]d ind i %[3]s. +issue.action.approve = @%[1]s godkendte denne pull-anmodning. +issue.action.reject = @%[1]s anmodede om รฆndringer pรฅ denne pull-anmodning. +issue.action.review = @%[1]s kommenterede denne pull-anmodning. +issue.action.review_dismissed = @%[1]s afviste den seneste kontrol fra %[2]s for denne pull-anmodning. +issue.action.ready_for_review = @%[1]s markerede denne pull-anmodning klar til gennemgang. +issue.action.new = @%[1]s oprettede #%[2]d. +issue.in_tree_path = I %s: +release.new.subject = %s i %s udgivet +release.new.text = @%[1]s udgivet %[2]s i %[3]s +release.title = Title: %s +release.note = Note: +release.downloads = Downloads: +release.download.zip = Kildekode (ZIP) +release.download.targz = Kildekode (TAR.GZ) +repo.transfer.subject_to = %s รธnsker at overfรธre depotet "%s" til %s +repo.transfer.subject_to_you = %s รธnsker at overfรธre depotet "%s" til dig +repo.transfer.to_you = dig +repo.transfer.body = For at acceptere eller afvise det, besรธg %s eller ignorer det. +repo.collaborator.added.subject = %s fรธjede dig til %s som samarbejdspartner +repo.collaborator.added.text = Du er blevet tilfรธjet som samarbejdspartner til depotet: +team_invite.subject = %[1]s har inviteret dig til at deltage i %[2]s organisationen +team_invite.text_1 = %[1]s har inviteret dig til at deltage i teamet %[2]s i organisationen %[3]s. +team_invite.text_2 = Klik venligst pรฅ fรธlgende link for at blive medlem af holdet: +team_invite.text_3 = Note: Denne invitation var beregnet til %[1]s. Hvis du ikke forventede denne invitation, kan du ignorere denne e-mail. + +[modal] +yes = Ja +no = Nej +confirm = Bekรฆrft +cancel = Annuller +modify = Updatere + +[form] +UserName = Brugernavn +FullName = Fulde navn +Description = Beskrivelse +Pronouns = Stedord +Biography = Biografi +Website = Websted +Location = Lokation +RepoName = Depot navn +Email = E-mailadresse +Password = Adgangskode +Retype = Bekrรฆft adgangskode +PayloadUrl = Payload URL +TeamName = Holdnavn +AuthName = Autorisationsnavn +AdminEmail = Admin email +To = Gren navn +AccessToken = Adgangstoken +NewBranchName = Nyt gren navn +CommitSummary = Commit oversigt +CommitMessage = Commit besked +CommitChoice = Commit valg +TreeName = Fil sti +Content = Indhold +SSPISeparatorReplacement = Separator +SSPIDefaultLanguage = Standard sprog +require_error = ` mรฅ ikke vรฆre tomt.` +alpha_dash_error = ` bรธr kun indeholde alfanumeriske, bindestreg ("-") og understregningstegn ("_").` +alpha_dash_dot_error = ` bรธr kun indeholde alfanumeriske tegn, bindestreg ("-"), understregning ("_") og prik ("".").` +git_ref_name_error = ` skal vรฆre et veludformet Git-referencenavn.` +size_error = ` skal vรฆre stรธrrelse %s.` +min_size_error = ` skal indeholde mindst %s tegn.` +email_error = ` er ikke en gyldig e-mailadresse.` +url_error = `"%s" er ikke en gyldig URL.` +include_error = ` skal indeholde understreng "%s".` +glob_pattern_error = ` globmรธnster er ugyldigt: %s.` +regex_pattern_error = ` regex-mรธnster er ugyldigt: %s.` +invalid_group_team_map_error = ` mapping er ugyldig: %s` +unknown_error = Ukendt fejl: +captcha_incorrect = CAPTCHA-koden er forkert. +password_not_match = Adgangskoderne stemmer ikke overens. +lang_select_error = Vรฆlg et sprog fra listen. +username_been_taken = Brugernavnet er allerede taget. +username_change_not_local_user = Ikke-lokale brugere mรฅ ikke รฆndre deres brugernavn. +repo_name_been_taken = Depotnavnet er allerede brugt. +repository_force_private = Tving Privat er aktiveret: private depoter kan ikke gรธres offentlige. +repository_files_already_exist = Der findes allerede filer for dette depot. Kontakt systemadministratoren. +repository_files_already_exist.delete = Der findes allerede filer for dette depot. Du skal slette dem. +visit_rate_limit = Fjernbesรธg adresseret takstbegrรฆnsning. +2fa_auth_required = Fjernbesรธg krรฆvede godkendelse af to faktorer. +org_name_been_taken = Organisationsnavnet er allerede taget. +team_name_been_taken = Holdnavnet er allerede taget. +team_no_units_error = Tillad adgang til mindst รฉn depotsektion. +email_been_used = E-mailadressen er allerede brugt. +email_invalid = E-mailadressen er ugyldig. +openid_been_used = OpenID-adressen "%s" er allerede brugt. +password_complexity = Adgangskoden opfylder ikke kompleksitetskravene: +password_lowercase_one = Mindst รฉt lille bogstav +password_uppercase_one = Mindst รฉt stort tegn +password_digit_one = Mindst รฉt ciffer +enterred_invalid_repo_name = Det depotnavn, du indtastede, er forkert. +enterred_invalid_org_name = Det organisationsnavn, du har indtastet, er forkert. +enterred_invalid_owner_name = Det nye ejernavn er ikke gyldigt. +enterred_invalid_password = Den adgangskode, du indtastede, er forkert. +unset_password = Login-brugeren har ikke angivet adgangskoden. +user_not_exist = Brugeren eksisterer ikke. +team_not_exist = Holdet eksisterer ikke. +last_org_owner = Du kan ikke fjerne den sidste bruger fra "ejere"-teamet. Der skal vรฆre mindst รฉn ejer for en organisation. +duplicate_invite_to_team = Brugeren var allerede inviteret som et holdmedlem. +organization_leave_success = Du har forladt organisationen %s. +invalid_ssh_key = Kan ikke bekrรฆfte din SSH-nรธgle: %s +invalid_gpg_key = Kan ikke bekrรฆfte din GPG-nรธgle: %s +invalid_ssh_principal = Ugyldig principal: %s +unable_verify_ssh_key = Kan ikke bekrรฆfte SSH-nรธglen, dobbelttjek den for fejl. +auth_failed = Godkendelse mislykkedes: %v +still_own_repo = Din konto ejer et eller flere depoter, slet eller overfรธr dem fรธrst. +still_has_org = Din konto er medlem af en eller flere organisationer, forlad dem fรธrst. +still_own_packages = Din konto ejer en eller flere pakker, slet dem fรธrst. +org_still_own_packages = Denne organisation ejer stadig en eller flere pakker, slet dem fรธrst. +target_branch_not_exist = Gren mรฅlet eksisterer ikke. +admin_cannot_delete_self = Du kan ikke slette dig selv, nรฅr du er administrator. Fjern venligst dine administratorrettigheder fรธrst. +required_prefix = Input skal starte med "%s" +username_error = ` kan kun indeholde alfanumeriske tegn ("0-9","a-z","A-Z"), bindestreg ("-"), understregning ("_") og prik ("."). Det kan ikke begynde eller slutte med ikke-alfanumeriske tegn, og pรฅ hinanden fรธlgende ikke-alfanumeriske tegn er ogsรฅ forbudt.` +max_size_error = ` mรฅ hรธjst indeholde %s tegn.` +repository_files_already_exist.adopt_or_delete = Der findes allerede filer for dette depot. Enten adopter dem eller slet dem. +org_still_own_repo = Denne organisation ejer stadig et eller flere depoter, slet eller overfรธr dem fรธrst. +username_error_no_dots = ` kan kun indeholde alfanumeriske tegn ("0-9","a-z","A-Z"), bindestreg ("-") og understregning ("_"). Det kan ikke begynde eller slutte med ikke-alfanumeriske tegn, og pรฅ hinanden fรธlgende ikke-alfanumeriske tegn er ogsรฅ forbudt.` +username_password_incorrect = Brugernavn eller adgangskode er forkert. +repository_files_already_exist.adopt = Filer findes allerede for dette depot og kan kun adopteres. +password_special_one = Mindst รฉt specialtegn (tegnsรฆtning, parenteser, anfรธrselstegn osv.) +unsupported_login_type = Login typen understรธttes ikke for at slette kontoen. +cannot_add_org_to_team = En organisation kan ikke tilfรธjes som et holdmedlem. +must_use_public_key = Nรธglen du har angivet er en privat nรธgle. Lad vรฆre med at uploade din private nรธgle nogen steder. Brug din offentlige nรธgle i stedet. + +[user] +change_avatar = Skift din avatarโ€ฆ +joined_on = Tilmeldte sig den %s +repositories = Depoter +activity = Offentlig aktivitet +followers.title.one = Fรธlger +followers.title.few = Fรธlgere +following.title.one = Fรธlger +following.title.few = Fรธlger +followers_one = %d fรธlger +followers_few = %d fรธlgere +following_one = %d fรธlger +following_few = %d fรธlger +follow = Fรธlg +unfollow = Fjern fรธlg +block_user = Bloker bruger +block_user.detail = Bemรฆrk venligst, at blokering af en bruger har andre effekter, sรฅsom: +block_user.detail_1 = I vil holde op med at fรธlge hinanden og vil ikke vรฆre i stand til at fรธlge hinanden. +block_user.detail_2 = Denne bruger vil ikke vรฆre i stand til at interagere med de depoter, du ejer, eller de problemer og kommentarer, du har oprettet. +block_user.detail_3 = I vil ikke vรฆre i stand til at tilfรธje hinanden som depot-samarbejdspartnere. +follow_blocked_user = Du kan ikke fรธlge denne bruger, fordi du har blokeret denne bruger, eller denne bruger har blokeret dig. +starred = Stjernemarkerede depoter +watched = Overvรฅgede depoter +code = Kode +overview = Oversigt +block = Block +user_bio = Biografi +email_visibility.limited = Din e-mailadresse er synlig for alle godkendte brugere +show_on_map = Vis dette sted pรฅ et kort +settings = Brugerindstillinger +public_activity.visibility_hint.admin_public = Denne aktivitet er synlig for alle, men som administrator kan du ogsรฅ se interaktioner i private rum. +public_activity.visibility_hint.self_private = Din aktivitet er kun synlig for dig og instans-administratorerne. Konfigurer. +public_activity.visibility_hint.admin_private = Denne aktivitet er synlig for dig, fordi du er administrator, men brugeren รธnsker, at den forbliver privat. +form.name_reserved = Brugernavnet "%s" er reserveret. +form.name_pattern_not_allowed = Mรธnsteret "%s" er ikke tilladt i et brugernavn. +form.name_chars_not_allowed = Brugernavnet "%s" indeholder ugyldige tegn. +unblock = Fjern blokering +projects = Projekter +disabled_public_activity = Denne bruger har deaktiveret den offentlige synlighed af aktiviteten. +public_activity.visibility_hint.self_public = Din aktivitet er synlig for alle, undtagen interaktioner i private rum. Konfigurer. +public_activity.visibility_hint.self_private_profile = Din aktivitet er kun synlig for dig og instans-administratorerne, fordi din profil er privat. Konfigurer. + +[settings] +profile = Profil +account = Konto +appearance = Udseende +password = Adgangskode +security = Sikkerhed +avatar = Avatar +ssh_gpg_keys = SSH / GPG nรธgler +applications = Applikationer +orgs = Organisationer +repos = Depoter +delete = Slet konto +twofa = To-faktor-godkendelse (TOTP) +organization = Organisationer +uid = UID +webauthn = To-faktor-godkendelse (sikkerhedsnรธgler) +blocked_users = Blokerede brugere +public_profile = Offentlig profil +location_placeholder = Del din omtrentlige placering med andre +password_username_disabled = Ikke-lokale brugere mรฅ ikke รฆndre deres brugernavn. Kontakt venligst din webstedsadministrator for flere detaljer. +full_name = Fulde navn +website = Websted +location = Lokation +pronouns = Stedord +pronouns_custom = Brugerdefineret +pronouns_unspecified = Uspecificeret +update_theme = Skift tema +update_language = Skift sprog +update_language_success = Sproget er blevet opdateret. +update_profile_success = Din profil er blevet opdateret. +change_username = Dit brugernavn er blevet รฆndret. +change_username_redirect_prompt = Det gamle brugernavn vil omdirigere, indtil nogen gรธr krav pรฅ det. +continue = Fortsรฆt +cancel = Annuller +language = Sprog +language.title = Standard sprog +language.description = Dette sprog gemmes pรฅ din konto og bruges som standard, nรฅr du logger ind. +ui = Tema +additional_repo_units_hint = Foreslรฅ at aktivere yderligere depotenheder +additional_repo_units_hint_description = Vis et "Aktiver mere"-tip for depoter, der ikke har alle tilgรฆngelige enheder aktiveret. +update_hints = Opdater tips +hints = Tips +update_hints_success = Tips er blevet opdateret. +hidden_comment_types = Skjulte kommentartyper +hidden_comment_types.ref_tooltip = Kommentarer, hvor dette problem blev refereret fra en anden problem/commit/โ€ฆ +hidden_comment_types.issue_ref_tooltip = Kommentarer, hvor brugeren รฆndrer grenen/taget, der er knyttet til problemet +comment_type_group_reference = Reference +comment_type_group_label = Etiket +comment_type_group_milestone = Milepรฆl +comment_type_group_assignee = Udpegningsmodtager +comment_type_group_title = Title +comment_type_group_branch = Gren +comment_type_group_time_tracking = Tidsregistrering +comment_type_group_deadline = Tidsfrist +comment_type_group_dependency = Dependency +comment_type_group_lock = Lรฅs status +comment_type_group_review_request = Gennemgรฅ anmodning +comment_type_group_pull_request_push = Tilfรธjet commits +comment_type_group_project = Projekt +comment_type_group_issue_ref = Problem henvisning +privacy = Privatliv +keep_activity_private = Skjul aktivitet fra profilsiden +lookup_avatar_by_mail = Slรฅ avatar up efter e-mailadresse +enable_custom_avatar = Brug tilpasset avatar +delete_current_avatar = Slet nuvรฆrende avatar +uploaded_avatar_not_a_image = Den uploadede fil er ikke et billede. +update_avatar_success = Din avatar er blevet opdateret. +update_user_avatar_success = Brugerens avatar er blevet opdateret. +change_password = Skift adgangskode +update_password = Opdater adgangskode +old_password = Nuvรฆrende adgangskode +new_password = Ny adgangskode +retype_new_password = Bekrรฆft ny adgangskode +password_incorrect = Den aktuelle adgangskode er forkert. +password_change_disabled = Ikke-lokale brugere kan ikke opdatere deres adgangskode via Forgejo-webgrรฆnsefladen. +manage_emails = Administrer e-mailadresser +manage_themes = Standard tema +manage_openid = OpenID-adresser +theme_desc = Dette tema vil blive brugt til webgrรฆnsefladen, nรฅr du er logget ind. +primary = Primรฆr +activated = Aktiveret +requires_activation = Krรฆver aktivering +primary_email = Gรธr til primรฆr +activate_email = Send aktivering +activations_pending = Aktiveringer afventer +can_not_add_email_activations_pending = Der er en afventende aktivering, prรธv igen om et par minutter, hvis du vil tilfรธje en ny e-mail. +delete_email = Slet +email_deletion = Fjern e-mailadresse +email_deletion_success = E-mailadressen er blevet fjernet. +theme_update_success = Dit tema blev opdateret. +theme_update_error = Det valgte tema findes ikke. +openid_deletion = Fjern OpenID-adresse +openid_deletion_desc = Fjernelse af denne OpenID-adresse fra din konto vil forhindre dig i at logge ind med den. Fortsรฆtte? +openid_deletion_success = OpenID-adressen er blevet fjernet. +add_new_email = Tilfรธj e-mailadresse +add_new_openid = Tilfรธj ny OpenID URI +language.localization_project = Hjรฆlp os med at oversรฆtte Forgejo til dit sprog! Fรฅ flere oplysninger. +update_language_not_found = Sprog "%s" er ikke tilgรฆngeligt. +hidden_comment_types_description = Kommentartyper, der er markeret her, vil ikke blive vist pรฅ problemsider. Hvis du f.eks. markerer "Etiket", fjernes alle " tilfรธjede/fjernede " kommentarer. +update_profile = Opdater profil +change_username_prompt = Note: ร†ndring af dit brugernavn รฆndrer ogsรฅ din konto-URL. +biography_placeholder = Fortรฆl andre lidt om dig selv! (Markdown understรธttes) +profile_desc = Styr, hvordan din profil vises til andre brugere. Din primรฆre e-mailadresse vil blive brugt til meddelelser, gendannelse af adgangskode og webbaserede Git-operationer. +saved_successfully = Dine indstillinger blev gemt. +keep_activity_private.description = Din offentlige aktivitet vil kun vรฆre synlig for dig og instans-administratorerne. +email_desc = Din primรฆre e-mailadresse vil blive brugt til meddelelser, gendannelse af adgangskode og, forudsat at den ikke er skjult, webbaserede Git-operationer. +uploaded_avatar_is_too_big = Den uploadede filstรธrrelse (%d KiB) overstiger den maksimale stรธrrelse (%d KiB). +email_deletion_desc = E-mailadressen og relaterede oplysninger vil blive fjernet fra din konto. Git commits af denne e-mailadresse forbliver uรฆndret. Fortsรฆtte? +choose_new_avatar = Vรฆlg ny avatar +update_avatar = Opdater avatar +change_password_success = Din adgangskode er blevet opdateret. Log ind med din nye adgangskode fra nu af. +add_email = Tilfรธj e-mailadresse +add_openid = Tilfรธj OpenID URI +add_email_confirmation_sent = En bekrรฆftelses-e-mail er blevet sendt til "%s". For at bekrรฆfte din e-mailadresse, tjek venligst din indbakke og fรธlg det medfรธlgende link inden for de nรฆste %s. +add_openid_success = Den nye OpenID-adresse er blevet tilfรธjet. +keep_email_private = Skjul e-mailadresse +openid_desc = OpenID lader dig uddelegere godkendelse til en ekstern udbyder. +manage_ssh_keys = Administrer SSH-nรธgler +manage_ssh_principals = Administrer SSH-certifikatprincippere +manage_gpg_keys = Administrer GPG-nรธgler +add_key = Tilfรธj nรธgle +ssh_desc = Disse offentlige SSH-nรธgler er knyttet til din konto. De tilsvarende private nรธgler giver fuld adgang til dine depoter. SSH-nรธgler, der er blevet bekrรฆftet, kan bruges til at bekrรฆfte SSH-signerede Git-commits. +principal_desc = Disse SSH-certifikatprincipper er knyttet til din konto og giver fuld adgang til dine depoter. +ssh_helper = Har du brug for hjรฆlp? Se vejledningen til at oprette dine egne SSH-nรธgler eller lรธse almindelige problemer a> du kan stรธde pรฅ nรฅr du bruger SSH. +gpg_helper = Har du brug for hjรฆlp? Se vejledningen om GPG. +key_content_gpg_placeholder = Begynder med "-----BEGIN PGP PUBLIC KEY BLOCK-----" +add_new_principal = Tilfรธj principal +ssh_key_been_used = Denne SSH-nรธgle er allerede blevet tilfรธjet til serveren. +ssh_key_name_used = En SSH-nรธgle med samme navn findes allerede pรฅ din konto. +ssh_principal_been_used = Denne principal er allerede blevet tilfรธjet til serveren. +gpg_key_id_used = Der findes allerede en offentlig GPG-nรธgle med samme id. +gpg_no_key_email_found = Denne GPG-nรธgle matcher ikke nogen aktiveret e-mail-adresse tilknyttet din konto. Det kan stadig tilfรธjes, hvis du underskriver det medfรธlgende token. +gpg_key_matched_identities = Matchede identiteter: +gpg_key_verified = Verificeret nรธgle +gpg_key_verified_long = Nรธglen er blevet bekrรฆftet med et token og kan bruges til at bekrรฆfte commits, der matcher alle aktiverede e-mailadresser for denne bruger ud over eventuelle matchede identiteter for denne nรธgle. +gpg_key_verify = Bekrรฆft +gpg_invalid_token_signature = Den medfรธlgende GPG-nรธgle, signatur og token stemmer ikke overens, eller token er forรฆldet. +gpg_token_required = Du skal angive en underskrift for nedenstรฅende token +gpg_token = Token +gpg_token_signature = Pansret GPG-signatur +key_signature_gpg_placeholder = Begynder med "-----BEGIN PGP SIGNATURE-----" +verify_gpg_key_success = GPG-nรธglen "%s" er blevet bekrรฆftet. +ssh_key_verified = Verificeret nรธgle +ssh_token_required = Du skal angive en underskrift for nedenstรฅende token +ssh_token = Token +ssh_token_help = Du kan generere en signatur ved at bruge: +ssh_token_signature = Pansret SSH signatur +key_signature_ssh_placeholder = Begynder med "-----BEGIN SSH SIGNATURE-----" +verify_ssh_key_success = SSH-nรธglen "%s" er blevet bekrรฆftet. +subkeys = Undernรธgler +key_name = Nรธglenavn +key_id = Nรธgle ID +key_content = Indhold +principal_content = Indhold +add_key_success = SSH-nรธglen "%s" er blevet tilfรธjet. +add_principal_success = SSH-certifikatprincippet "%s" er blevet tilfรธjet. +delete_key = Slet +ssh_key_deletion = Slet SSH-nรธgle +gpg_key_deletion = Slet GPG-nรธglen +ssh_principal_deletion = Slet SSH Certificate Principal +gpg_key_deletion_desc = Fjernelse af en GPG-nรธgle afbekrรฆfter commits, der er underskrevet af den. Fortsรฆtte? +ssh_principal_deletion_desc = Fjernelse af en SSH Certificate Principal tilbagekalder dens adgang til din konto. Fortsรฆtte? +ssh_key_deletion_success = SSH-nรธglen er blevet fjernet. +ssh_principal_deletion_success = Principal er blevet fjernet. +added_on = Tilfรธjet den %s +valid_until_date = Gyldig indtil %s +last_used = Sidst brugt pรฅ +no_activity = Ingen nylig aktivitet +can_read_info = Lรฆs +can_write_info = Skriv +token_state_desc = Dette token er blevet brugt inden for de sidste 7 dage +principal_state_desc = Denne principal er blevet brugt inden for de sidste 7 dage +show_openid = Vis pรฅ profil +hide_openid = Skjul fra profil +ssh_externally_managed = Denne SSH-nรธgle administreres eksternt for denne bruger +manage_access_token = Adgangstoken +generate_new_token = Generer nyt token +tokens_desc = Disse tokens giver adgang til din konto ved hjรฆlp af Forgejo API. +token_name = Token navn +generate_token = Generer token +generate_token_success = Dit nye token er blevet genereret. Kopier den nu, da den ikke vises igen. +delete_token = Slet +access_token_deletion = Slet adgangstoken +access_token_deletion_desc = Sletning af et token vil tilbagekalde adgangen til din konto for applikationer, der bruger den. Dette kan ikke fortrydes. Fortsรฆtte? +repo_and_org_access = Depot og organisationsadgang +permissions_public_only = Kun offentlig +permissions_access_all = Alle (offentlige, private og begrรฆnsede) +select_permissions = Vรฆlg tilladelser +permission_no_access = Ingen adgang +permission_read = Lรฆs +permission_write = Lรฆs og skriv +permissions_list = Tilladelser: +manage_oauth2_applications = Administrer OAuth2-applikationer +edit_oauth2_application = Rediger OAuth2-applikation +add_email_success = Den nye e-mailadresse er blevet tilfรธjet. +key_content_ssh_placeholder = Begynder med "ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ecdsa-sha2-nistp256@openssh.com", eller "sk-ssh-ed25519@openssh.com" +gpg_key_matched_identities_long = De indlejrede identiteter i denne nรธgle matcher de fรธlgende aktiverede e-mailadresser for denne bruger. Commits, der matcher disse e-mailadresser, kan bekrรฆftes med denne nรธgle. +gpg_key_deletion_success = GPG-nรธglen er blevet fjernet. +email_preference_set_success = E-mail-prรฆference er blevet indstillet. +keep_email_private_popup = Dette vil skjule din e-mailadresse fra din profil. Det vil ikke lรฆngere vรฆre standard for commits foretaget via webgrรฆnsefladen, sรฅsom filupload og redigeringer, og vil ikke blive brugt til merge commits. I stedet kan en speciel adresse %s bruges til at knytte commits til din konto. Bemรฆrk, at รฆndring af denne mulighed ikke vil pรฅvirke eksisterende commits. +gpg_desc = Disse offentlige GPG-nรธgler er knyttet til din konto og bruges til at bekrรฆfte dine commits. Opbevar dine private nรธgler sikkert, da de giver dig mulighed for at underskrive commits med din identitet. +gpg_token_help = Du kan generere en signatur ved at bruge: +ssh_key_verified_long = Nรธglen er blevet bekrรฆftet med et token og kan bruges til at bekrรฆfte commits, der matcher enhver aktiveret e-mail-adresse for denne bruger. +ssh_key_deletion_desc = Fjernelse af en SSH-nรธgle tilbagekalder dens adgang til din konto. Fortsรฆtte? +ssh_signonly = SSH er i รธjeblikket deaktiveret, sรฅ disse nรธgler bruges kun til bekrรฆftelse af commit signatur. +at_least_one_permission = Du skal vรฆlge mindst รฉn tilladelse for at oprette et token +ssh_key_verify = Bekrรฆft +ssh_invalid_token_signature = Den angivne SSH-nรธgle, signatur eller token stemmer ikke overens, eller token er forรฆldet. +add_gpg_key_success = GPG-nรธglen "%s" er blevet tilfรธjet. +valid_forever = Gyldig for evigt +ssh_disabled = SSH er deaktiveret +key_state_desc = Denne nรธgle er blevet brugt inden for de sidste 7 dage +generate_token_name_duplicate = %s er allerede blevet brugt som applikationsnavn. Brug venligst en ny. +delete_token_success = Tokenet er blevet slettet. Applikationer, der bruger den, har ikke lรฆngere adgang til din konto. +access_token_desc = Valgte tokentilladelser begrรฆnser kun autorisation til de tilsvarende API-ruter. Lรฆs dokumentationen for at fรฅ flere oplysninger. +oauth2_applications_desc = OAuth2-applikationer gรธr det muligt for din tredjepartsapplikation at godkende brugere sikkert i denne Forgejo-instans. +remove_oauth2_application = Slet OAuth2-applikation +remove_oauth2_application_desc = Sletning af en OAuth2-applikation vil tilbagekalde adgangen til alle signerede adgangstokens. Vil du fortsรฆtte? +remove_oauth2_application_success = Ansรธgningen er blevet slettet. +create_oauth2_application = Opret en ny OAuth2-applikation +create_oauth2_application_button = Opret applikation +create_oauth2_application_success = Du har oprettet en ny OAuth2-applikation. +update_oauth2_application_success = Du har opdateret OAuth2-applikationen. +oauth2_application_name = Applikationsnavn +oauth2_redirect_uris = Omdiriger URI'er. Brug venligst en ny linje for hver URI. +save_application = Gem +oauth2_client_id = Klient ID +oauth2_client_secret = Klient hemmelighed +oauth2_regenerate_secret = Genskab hemmeligheden +oauth2_regenerate_secret_hint = Har du mistet din hemmelighed? +oauth2_client_secret_hint = Hemmeligheden vil ikke blive vist igen, nรฅr du forlader eller opdaterer denne side. Sรธrg for, at du har gemt den. +oauth2_application_edit = Redigere +oauth2_confidential_client = Fortrolig klient. Vรฆlg for apps, der holder hemmeligheden fortrolig, sรฅsom webapps. Vรฆlg ikke for indbyggede apps, herunder desktop- og mobilapps. \ No newline at end of file diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 12c241accc..2b28ab13af 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -814,7 +814,7 @@ manage_emails=E-Mail-Adressen verwalten manage_themes=Standard-Theme manage_openid=OpenID-Adressen email_desc=Deine primรคre E-Mail-Adresse wird fรผr Benachrichtigungen, Passwort-Wiederherstellung und, sofern sie nicht versteckt ist, web-basierte Git-Operationen verwendet. -theme_desc=Dies wird dein Standard-Theme auf der Seite sein. +theme_desc=Dieses Thema wird fรผr die Weboberflรคche verwendet, wenn du angemeldet bist. primary=Primรคr activated=Aktiviert requires_activation=Erfordert Aktivierung @@ -1097,7 +1097,7 @@ issue_labels=Labels issue_labels_helper=Wรคhle eine Label-Sammlung license=Lizenz license_helper=Wรคhle eine Lizenz -license_helper_desc=Eine Lizenz regelt, was andere mit deinem Code tun (oder nicht tun) kรถnnen. Unsicher, welches fรผr dein Projekt die Richtige ist? Siehe Choose a license. +license_helper_desc=Eine Lizenz regelt, was andere mit deinem Code tun (oder nicht tun) kรถnnen. Unsicher, welches fรผr dein Projekt die Richtige ist? Siehe Choose a license. readme=README readme_helper=Wรคhle eine README-Vorlage readme_helper_desc=Hier kannst du eine komplette Beschreibung fรผr dein Projekt schreiben. @@ -2490,8 +2490,8 @@ settings.archive.text=Durch das Archivieren wird ein Repo vollstรคndig schreibge settings.archive.success=Das Repo wurde erfolgreich archiviert. settings.archive.error=Beim Versuch, das Repository zu archivieren, ist ein Fehler aufgetreten. Weitere Details finden sich im Log. settings.archive.error_ismirror=Du kannst kein gespiegeltes Repo archivieren. -settings.archive.branchsettings_unavailable=Branch-Einstellungen sind nicht verfรผgbar wenn das Repo archiviert ist. -settings.archive.tagsettings_unavailable=Tag Einstellungen sind nicht verfรผgbar, wenn das Repo archiviert wurde. +settings.archive.branchsettings_unavailable=Branch-Einstellungen sind nicht verfรผgbar in archivierten Repos. +settings.archive.tagsettings_unavailable=Tag-Einstellungen sind nicht verfรผgbar in archivierten Repos. settings.unarchive.button=Archivierung zurรผcksetzen settings.unarchive.header=Archivierung dieses Repositorys zurรผcksetzen settings.unarchive.text=Durch das Aufheben der Archivierung kann das Repo wieder Commits und Pushes sowie neue Issues und Pull-Requests empfangen. @@ -2690,7 +2690,7 @@ error.csv.too_large=Diese Datei kann nicht gerendert werden, da sie zu groรŸ ist error.csv.unexpected=Diese Datei kann nicht gerendert werden, da sie ein unerwartetes Zeichen in Zeile %d und Spalte %d enthรคlt. error.csv.invalid_field_count=Diese Datei kann nicht gerendert werden, da sie eine falsche Anzahl an Feldern in Zeile %d hat. rss.must_be_on_branch = Du musst auf einem Branch sein, um einen RSS-Feed zu haben. -new_repo_helper = Ein Repository enthรคlt alle Projektdateien inklusive der Revisionshistorie. Bereits woanders gehostet? Repository migrieren. +new_repo_helper = Ein Repository enthรคlt alle Projektdateien inklusive der Revisionshistorie. Bereits woanders gehostet? Repository migrieren. issues.comment.blocked_by_user = Du kannst kein Kommentar fรผr dieses Issue erstellen, weil du vom Repository-Besitzer oder dem Autoren des Issues blockiert wurdest. clone_in_vscodium = In VSCodium klonen settings.units.add_more = Mehr aktivieren @@ -2704,7 +2704,7 @@ settings.add_collaborator_blocked_them = Der Mitarbeiter konnte nicht hinzugefรผ settings.wiki_rename_branch_main = Den Wiki-Branch-Namen normalisieren settings.enter_repo_name = Gib den Besitzer- und den Repository-Namen genau wie angezeigt ein: settings.wiki_branch_rename_success = Der Branch-Name des Repository-Wikis wurde erfolgreich normalisiert. -settings.archive.mirrors_unavailable = Spiegel sind nicht verfรผgbar, wenn das Repo archiviert ist. +settings.archive.mirrors_unavailable = Spiegel sind nicht verfรผgbar in archivierten Repos. pulls.blocked_by_user = Du kannst keinen Pull-Request in diesem Repository erstellen, weil du vom Repository-Besitzer blockiert wurdest. settings.add_collaborator_blocked_our = Der Mitarbeiter konnte nicht hinzugefรผgt werden, weil der Repository-Besitzer ihn blockiert hat. issues.blocked_by_user = Du kannst kein Issue in diesem Repository erstellen, weil du vom Repository-Besitzer blockiert wurdest. @@ -3686,12 +3686,12 @@ conan.registry=Diese Registry รผber die Kommandozeile einrichten: conan.install=Um das Paket mit Conan zu installieren, fรผhre den folgenden Befehl aus: conda.registry=Richte diese Registry als Conda-Repository in deiner .condarc-Datei ein: conda.install=Um das Paket mit Conda zu installieren, fรผhre den folgenden Befehl aus: -container.details.type=Container-Image Typ +container.details.type=Abbildtyp container.details.platform=Plattform container.pull=Downloade das Container-Image aus der Kommandozeile: container.digest=Digest container.multi_arch=Betriebsystem / Architektur -container.layers=Container-Image Ebenen +container.layers=Abbildebenen container.labels=Labels container.labels.key=Schlรผssel container.labels.value=Wert diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index b3864d4e83..12f88a5847 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -319,7 +319,7 @@ default_keep_email_private=Piilota sรคhkรถpostiosoitteet oletuksena default_keep_email_private.description=Piilota oletusarvoisesti uusien kรคyttรคjรคtilien sรคhkรถpostiosoitteet estรครคksesi tietojen vuotamisen rekisterรถinnin yhteydessรค. default_enable_timetracking=Ota ajanseuranta oletusarvoisesti kรคyttรถรถn default_enable_timetracking.description=Salli uusien repositorioiden aikaseurannan kรคyttรถรถnotto oletusarvoisesti. -no_reply_address=Piilotettu sรคhkรถpostin verkkotunnus +no_reply_address=Piilotetun sรคhkรถpostin verkkotunnus no_reply_address_helper=Verkkotunnuksen nimi kรคyttรคjille, joilla on piilotettu sรคhkรถpostiosoite. Esimerkiksi kรคyttรคjรคtunnus 'joe' kirjataan Git-palveluun nimellรค 'joe@noreply.example.org' jos piilotetun sรคhkรถpostiosoitteen arvoksi on asetettu 'noreply.example.org'. password_algorithm=Salasanan hajautusalgoritmi enable_update_checker_helper_forgejo = Se tarkistaa tietyin vรคliajoin uusia Forgejo-versioita tutkimalla sen TXT DNS record -tietoja osoitteesta release.forgejo.org . @@ -461,6 +461,9 @@ change_unconfirmed_email = Jos annoit vรครคrรคn sรคhkรถpostiosoitteen rekisterรถ invalid_code_forgot_password = Vahvistuskoodisi on virheellinen tai vanhentunut. Napsauta tรคstรค aloittaaksesi uuden istunnon. openid_signin_desc = Kirjoita OpenID-URI:si. Esimerkki: alice.openid.example.org tai https://openid.example.org/alice. change_unconfirmed_email_summary = Vaihda sรคhkรถpostiosoite, johon aktivointisรคhkรถposti lรคhetetรครคn. +reset_password_wrong_user = Olet kirjautuneena tilillรค %s, mutta tilin palautuslinkki on tarkoitettu tilille %s +last_admin = Et voi poistaa viimeistรค yllรคpitรคjรครค. Yllรคpitรคjiรค tulee olla vรคhintรครคn yksi. +password_pwned = Valitsemasi salasana on varastettujen salasanojen listalla, eli se on paljastanut jossain julkisessa tietovuodossa. Kokeile asettaa eri salasana, ja jos kรคytรคt samaa salasanaa muissa palveluissa, vaihda kyseinen salasana. [mail] view_it_on=Nรคytรค %s @@ -629,6 +632,8 @@ following_one = %d seurataan block_user.detail = Huomaa, ettรค kรคyttรคjรคn estรคmisellรค on muita vaikutuksia, kuten: show_on_map = Nรคytรค paikka kartalla form.name_chars_not_allowed = Kรคyttรคjรคtunnus "%s" sisรคltรครค virheellisiรค merkkejรค. +follow_blocked_user = Et voi seurata tรคtรค kรคyttรคjรครค, koska olet estรคnyt kyseisen kรคyttรคjรคn tai kyseinen kรคyttรคjรค on estรคnyt sinut. +disabled_public_activity = Kรคyttรคjรค on poistanut kรคytรถstรค toiminnan julkisen nรคkyvyyden. [settings] @@ -684,7 +689,7 @@ keep_activity_private_popup=Tekee toiminnon nรคkyvรคn vain sinulle ja yllรคpitรค lookup_avatar_by_mail=Hae profiilikuva sรคhkรถpostin perusteella federated_avatar_lookup=Ulkopuolinen profiilikuvan haku -enable_custom_avatar=Ota kรคyttรถรถn mukautettu profiilikuva +enable_custom_avatar=Kรคytรค mukautettua profiilikuvaa choose_new_avatar=Valitse uusi profiilikuva update_avatar=Pรคivitรค profiilikuva delete_current_avatar=Poista nykyinen profiilikuva @@ -699,9 +704,9 @@ password_change_disabled=Ei-lokaalit kรคyttรคjรคt eivรคt voi pรคivittรครค salasa emails=Sรคhkรถposti osoitteet manage_emails=Hallitse sรคhkรถpostiosoitteita -manage_themes=Valitse oletusteema -manage_openid=Hallitse OpenID osoitteita -theme_desc=Tรคmรค on sivuston oletusteemasi. +manage_themes=Oletusteema +manage_openid=OpenID-osoitteet +theme_desc=Tรคtรค teemaa kรคytetรครคn verkkosivuston kรคyttรถliittymรคssรค, kun olet sisรครคnkirjautuneena. primary=Ensisijainen activated=Aktivoitu requires_activation=Vaatii aktivoinnin @@ -717,7 +722,7 @@ theme_update_error=Valittua teemaa ei lรถydy. openid_deletion=Poista OpenID-osoite openid_deletion_success=OpenID-osoite on poistettu. add_new_email=Lisรครค uusi sรคhkรถpostiosoite -add_new_openid=Lisรครค uusi OpenID URI +add_new_openid=Lisรครค uusi OpenID-URI add_email=Lisรครค sรคhkรถpostiosoite add_openid=Lisรครค OpenID URI add_email_success=Uusi sรคhkรถpostiosoite on lisรคtty. @@ -729,14 +734,14 @@ openid_desc=OpenID mahdollistaa todentamisen delegoinnin ulkopuoliselle palvelun manage_ssh_keys=Hallitse SSH-avaimia manage_gpg_keys=Hallitse GPG-avaimia add_key=Lisรครค avain -ssh_desc=Nรคmรค julkiset SSH-avaimet on liitetty tiliisi. Vastaavat yksityiset avaimet antavat tรคyden pรครคsyn repoihisi. -gpg_desc=Nรคmรค julkiset GPG-avaimet on liitetty tiliisi. Pidรค yksityiset avaimet turvassa, koska ne mahdollistavat committien todentamisen. +ssh_desc=Nรคmรค julkiset SSH-avaimet on liitetty tiliisi. Vastaavat yksityiset avaimet antavat tรคyden pรครคsyn repoihisi. Vahvistettuja SSH-avaimia voi kรคyttรครค SSH-allekirjoitettujen Git-kommittien vahvistamiseen. +gpg_desc=Nรคmรค julkiset GPG-avaimet on liitetty tiliisi, ja niitรค kรคytetรครคn kommittien vahvistamiseen. Pidรค yksityiset avaimet turvassa, koska ne mahdollistavat kommittien allekirjoittamisen sinun nimissรค. ssh_helper=Tarvitsetko apua? Tutustu GitHubin oppaaseen omien SSH-avainten luonnista tai yleisistรค ongelmista, joita voit kohdata SSH:n kanssa. gpg_helper=Tarvitsetko apua? Katso GitHubin opas GPG:stรค. add_new_key=Lisรครค SSH avain add_new_gpg_key=Lisรครค GPG-avain -key_content_ssh_placeholder=Alkaa sanoilla 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', tai 'sk-ssh-ed25519@openssh.com' -key_content_gpg_placeholder=Alkaa sanoilla '-----BEGIN PGP PUBLIC KEY BLOCK-----' +key_content_ssh_placeholder=Alkaa sanoilla "ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ecdsa-sha2-nistp256@openssh.com" tai "sk-ssh-ed25519@openssh.com" +key_content_gpg_placeholder=Alkaa sanoilla "-----BEGIN PGP PUBLIC KEY BLOCK-----" ssh_key_name_used=Samanniminen SSH avain on jo olemassa tilillรคsi. gpg_key_id_used=Julkinen GPG-avain samalla tunnuksella on jo olemassa. gpg_no_key_email_found=Tรคmรค GPG-avain ei vastaa mitรครคn tiliisi liitettyรค aktivoitua sรคhkรถpostiosoitetta. Se voidaan silti lisรคtรค, jos allekirjoitat annetun pรครคsymerkin. @@ -748,7 +753,7 @@ gpg_token=Pรครคsymerkki gpg_token_help=Voit luoda allekirjoituksen kรคyttรคen: gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig gpg_token_signature=Panssaroitu GPG-allekirjoitus -key_signature_gpg_placeholder=Alkaa sanoilla '-----BEGIN PGP SIGNATURE-----' +key_signature_gpg_placeholder=Alkaa sanoilla "-----BEGIN PGP SIGNATURE-----" ssh_key_verified=Vahvistettu avain ssh_key_verified_long=Avain on vahvistettu pรครคsymerkillรค ja sitรค voidaan kรคyttรครค todentamaan commitit, jotka vastaavat tรคmรคn kรคyttรคjรคn aktivoituja sรคhkรถpostiosoitteita. ssh_key_verify=Vahvista @@ -756,7 +761,7 @@ ssh_token_required=Sinun tรคytyy antaa allekirjoitus alla olevalle pรครคsymerkil ssh_token=Pรครคsymerkki ssh_token_help=Voit luoda allekirjoituksen kรคyttรคen: ssh_token_signature=Panssaroitu SSH-allekirjoitus -key_signature_ssh_placeholder=Alkaa sanoilla '-----BEGIN SSH SIGNATURE-----' +key_signature_ssh_placeholder=Alkaa sanoilla "-----BEGIN SSH SIGNATURE-----" subkeys=Aliavaimet key_id=Avain ID key_name=Avaimen nimi @@ -774,7 +779,7 @@ can_read_info=Luku can_write_info=Kirjoitus show_openid=Nรคytรค profiilissa hide_openid=Piilota profiilista -ssh_disabled=SSH pois kรคytรถstรค +ssh_disabled=SSH on pois kรคytรถstรค manage_social=Hallitse liitettyjรค sosiaalisia tilejรค manage_access_token=Hallitse pรครคsymerkkejรค @@ -795,7 +800,7 @@ create_oauth2_application=Luo uusi OAuth2-sovellus create_oauth2_application_button=Luo sovellus oauth2_application_name=Sovelluksen nimi save_application=Tallenna -oauth2_regenerate_secret=Luo secret uudelleen +oauth2_regenerate_secret=Luo salaisuus uudelleen oauth2_regenerate_secret_hint=Kadotitko secretin? oauth2_application_edit=Muokkaa @@ -811,10 +816,10 @@ twofa_enrolled=Tiliisi on otettu kรคyttรถรถn kaksivaiheinen vahvistus. Ota palau webauthn_nickname=Nimimerkki -manage_account_links=Hallitse linkitettyjรค tilejรค +manage_account_links=Yhdistetyt tilit manage_account_links_desc=Nรคmรค ulkoiset tilit on linkitetty Forgejo tiliisi. link_account=Yhdistรค tili -remove_account_link=Poista linkitetty tili +remove_account_link=Poista yhdistetty tili remove_account_link_desc=Linkitetyn tilin poistaminen peruuttaa pรครคsyn Forgejo-tiliisi linkitetyn tili kautta. Jatketaanko? remove_account_link_success=Linkitetty tili on poistettu. @@ -854,7 +859,7 @@ location_placeholder = Jaa likimรครคrรคinen sijaintisi muiden kanssa retype_new_password = Vahvista uusi salasana create_oauth2_application_success = Loit uuden OAuth2-sovelluksen. repos_none = Et omista yhtรคkรครคn repositoriota. -visibility.limited_tooltip = Nรคkyvissรค vain tunnistautuneille kรคyttรคjille +visibility.limited_tooltip = Nรคkyvissรค vain kirjautuneille kรคyttรคjille email_notifications.disable = Poista sรคhkรถposti-ilmoitukset kรคytรถstรค webauthn_register_key = Lisรครค turva-avain blocked_users = Estetyt kรคyttรคjรคt @@ -892,6 +897,28 @@ twofa_disable = Poista kaksivaiheinen todennus kรคytรถstรค twofa_disable_desc = Kaksivaiheisen todennuksen poistaminen asettaa tilisi aiempaa suurempaan uhkaan. Jatketaanko? update_language_not_found = Kieli "%s" ei ole kรคytettรคvissรค. change_username_prompt = Huomio: Kรคyttรคjรคtunnuksen vaihtaminen muuttaa myรถs tilisi URL-osoitteen. +oauth2_client_secret_hint = Tรคtรค salaisuutta ei nรคytetรค uudelleen, kun olet poistunut sivulta tai pรคivittรคnyt sivun. Varmista, ettรค olet ottanut salaisuuden talteen. +blocked_since = Estetty %s lรคhtien +user_unblock_success = Kรคyttรคjรคn esto on poistettu. +oauth2_redirect_uris = Uudelleenohjaus-URI:t. Kรคytรค uutta riviรค (newline) jokaista URI:a kohden. +oauth2_client_secret = Asiakkaan salaisuus +verify_ssh_key_success = SSH-avain "%s" on vahvistettu. +change_username_redirect_prompt = Vanha kรคyttรคjรคtunnus uudelleenohjaa, kunnes joku muu ottaa kรคyttรคjรคtunnuksen kรคyttรถnsรค. +uploaded_avatar_is_too_big = Lรคhetetyn tiedoston koko (%d KiB) ylittรครค enimmรคiskoon (%d KiB). +ssh_key_been_used = Tรคmรค SSH-avain on jo lisรคtty palvelimelle. +verify_gpg_key_success = GPG-avain "%s" on vahvistettu. +add_key_success = SSH-avain "%s" on lisรคtty. +add_gpg_key_success = GPG-avain "%s" on lisรคtty. +ssh_key_deletion_success = SSH-avain on poistettu. +valid_until_date = Kelvollinen %s asti +oauth2_client_id = Asiakkaan tunniste +email_notifications.onmention = Ilmoitus vain maininnasta +email_notifications.submit = Aseta valinta +email_notifications.andyourown = Ja omat ilmoitukset +key_state_desc = Tรคtรค avainta on kรคytetty viimeisen 7 pรคivรคn aikana +oauth2_application_create_description = OAuth2-sovellukset mahdollistavat kolmannen osapuolen sovelluksen pรครคsyn tilillesi tรคssรค instanssissa. +oauth2_confidential_client = Luottamuksellinen sovellus. Valitse sovelluksille, jotka pitรคvรคt salaisuuden luottamuksellisena, kuten web-sovelluksille. ร„lรค valitse natiiveille sovelluksille mukaan lukien tyรถpรถytรค- ja mobiilisovellukset. +ssh_key_deletion_desc = SSH-avaimen poistaminen kumoaa pรครคsyn tilillesi kyseistรค avainta kรคyttรคen. Jatketaanko? [repo] owner=Omistaja @@ -917,7 +944,7 @@ repo_gitignore_helper=Valitse .gitignore-mallit issue_labels=Ongelmien tunnisteet issue_labels_helper=Valitse pohja ongelmien nimilapuille. license=Lisenssi -license_helper=Valitse lisenssitiedosto. +license_helper=Valitse lisenssitiedosto readme=README auto_init=Alusta repo (Luo .gitignore, License ja README) create_repo=Luo repo @@ -1316,7 +1343,7 @@ activity.new_issues_count_1=Uusi ongelma activity.new_issues_count_n=uutta ongelmaa activity.new_issue_label=Avoinna activity.unresolved_conv_label=Auki -activity.published_release_label=Julkaistu +activity.published_release_label=Julkaisu activity.git_stats_pushed_1=on tyรถntรคnyt activity.git_stats_file_1=%d tiedosto activity.git_stats_file_n=%d tiedostoa @@ -1361,9 +1388,9 @@ settings.transfer.title=Siirrรค omistajuus settings.transfer_form_title=Syรถtรค repon nimi vahvistuksena: settings.transfer_notices_3=- Jos arkisto on yksityinen ja se siirretรครคn yksittรคiselle kรคyttรคjรคlle, tรคmรค toiminto varmistaa, ettรค kรคyttรคjรคllรค on ainakin lukuoikeudet (ja muuttaa kรคyttรถoikeuksia tarvittaessa). settings.transfer_owner=Uusi omistaja -settings.wiki_delete=Poista Wiki data +settings.wiki_delete=Poista wikidata settings.wiki_delete_desc=Repon wikin data poistaminen on pysyvรค eikรค voi peruuttaa. -settings.confirm_wiki_delete=Wiki datan poistaminen +settings.confirm_wiki_delete=Poista wikidata settings.wiki_deletion_success=Repon wiki data on poistettu. settings.delete=Poista tรคmรค repo settings.delete_desc=Repon poistaminen on pysyvรค eikรค voi peruuttaa. @@ -1384,7 +1411,7 @@ settings.webhook.body=Sisรคltรถ settings.githook_edit_desc=Jos koukku ei ole kรคytรถssรค, esitellรครคn esimerkkisisรคltรถ. Sisรคllรถn jรคttรคminen tyhjรคksi arvoksi poistaa tรคmรคn koukun kรคytรถstรค. settings.githook_name=Koukun nimi settings.githook_content=Koukun sisรคltรถ -settings.update_githook=Pรคivitys koukku +settings.update_githook=Pรคivitรค koukku settings.payload_url=Kohde URL settings.http_method=HTTP-menetelmรค settings.secret=Salaus @@ -1406,7 +1433,7 @@ settings.event_push_desc=Git push repoon. settings.event_repository=Repo settings.event_repository_desc=Repo luotu tai poistettu. settings.event_header_issue=Ongelmien tapahtumat -settings.event_issues=Ongelmat +settings.event_issues=Muokkaus settings.event_issues_desc=Ongelma avattu, suljettu, avattu uudelleen tai muokattu. settings.event_issue_assign=Ongelma mรครคritetty settings.event_issue_assign_desc=Ongelma osoitettu tai osoitus poistettu. @@ -1414,7 +1441,7 @@ settings.event_issue_label_desc=Ongelman tunnisteet pรคivitetty tai tyhjennetty. settings.event_issue_milestone_desc=Merkkipaalu lisรคtty, poistettu tai muokattu. settings.event_issue_comment_desc=Ongelman kommentti luotu, muokattu tai poistettu. settings.event_header_pull_request=Vetopyyntรถjen tapahtumat -settings.event_pull_request=Vetopyyntรถ +settings.event_pull_request=Muokkaus settings.event_package_desc=Paketti on luotu tai poistettu repossa. settings.active_helper=Tiedot kรคynnistetyistรค tapahtumista lรคhetetรครคn tรคhรคn webkoukun URL-osoitteeseen. settings.add_hook_success=Uusi webkoukku on lisรคtty. @@ -1883,7 +1910,7 @@ migrate.gitlab.description = Tee migraatio gitlab.comista tai muista GitLab-inst migrate.gitea.description = Tee migraatio gitea.comista tai muista Gitea-instansseista. repo_gitignore_helper_desc = Valitse mitรค tiedostoja ei seurata yleisimpien kielten mallipohjista. Tyypilliset artefaktit, joita eri kielten koostamistyรถkalut tuottavat, lisรคtรครคn .gitignore-tiedostoon oletusarvoisesti. milestones.filter_sort.latest_due_date = Kaukaisin mรครคrรคpรคivรค -license_helper_desc = Lisenssi mรครคrรครค, mitรค muut voivat ja eivรคt voi tehdรค koodillasi. Etkรถ ole varma, mikรค lisenssi soveltuu projektillesi? Lue ohje lisenssin valinnasta. +license_helper_desc = Lisenssi mรครคrรครค, mitรค muut voivat ja eivรคt voi tehdรค koodillasi. Etkรถ ole varma, mikรค lisenssi soveltuu projektillesi? Lue ohje lisenssin valinnasta. milestones.filter_sort.earliest_due_data = Lรคhin mรครคrรคpรคivรค issues.filter_type.reviewed_by_you = Katselmoitu toimestasi settings.units.overview = Yleisnรคkymรค @@ -1959,6 +1986,31 @@ editor.must_have_write_access = Sinulla tรคytyy olla kirjoitusoikeus tehdรคksesi issues.re_request_review = Pyydรค katselmointia uudelleen pulls.status_checks_details = Yksityiskohdat release.title_empty = Nimi ei voi olla tyhjรค. +archive.title = Tรคmรค repo on arkistoitu. Voit katsella sen sisรคltรคmiรค tiedostoja ja kloonata repon, mutta et voi pushata, avata ongelmia tai luoda vetopyyntรถjรค. +reactions_more = ja %d lisรครค +mirror_address = Kloonaa URL-osoitteesta +migrate_items_merge_requests = Yhdistรคmispyynnรถt +stars_remove_warning = Tรคmรค poistaa kaikki tรคhdet tรคstรค reposta. +archive.issue.nocomment = Tรคmรค repo on arkistoitu. Et voi kommentoida ongelmia. +archive.pull.nocomment = Tรคmรค repo on arkistoitu. Et voi kommentoida vetopyyntรถjรค. +settings.webhook_deletion_desc = Webkoukun poistaminen poistaa sen asetukset ja toimitushistorian. Jatketaanko? +settings.discord_icon_url.exceeds_max_length = Kuvakkeen URL-osoite voi sisรคltรครค enintรครคn 2048 merkkiรค +settings.event_wiki_desc = Wiki-sivu luotu, nimetty uudelleen, muokattu tai poistettu. +settings.event_pull_request_desc = Vetopyyntรถ avattu, suljettu, avattu uudelleen tai muokattu. +settings.protect_branch_name_pattern = Suojatun haaran nimen kaava +issues.dependency.add_error_dep_not_same_repo = Molempien ongelmien tulee olla samassa repossa. +settings.event_release = Julkaisu +pulls.merge_pull_request = Luo yhdistรคmiskommitti +settings.pull_mirror_sync_quota_exceeded = Kiintiรถ ylitetty, ei vedetรค muutoksia. +settings.wiki_rename_branch_main_notices_1 = Tรคtรค toimintoa EI VOI perua. +settings.webhook.test_delivery_desc_disabled = Aktivoi webkoukku testataksesi sitรค tekaistulla tapahtumalla. +settings.discord_icon_url = Kuvakkeen URL-osoite +settings.archive.branchsettings_unavailable = Haaran asetukset eivรคt ole saatavilla arkistoiduissa repoissa. +pulls.ready_for_review = Valmiina katselmointiin? +issues.time_spent_total = Kรคytetty kokonaisaika +settings.webhook.test_delivery_desc = Testaa tรคtรค webkoukkua tekaistulla tapahtumalla. +pulls.switch_comparison_type = Vaihda vertailutyyppiรค +settings.hooks_desc = Webkoukut tekevรคt automaattisesti HTTP POST -pyyntรถjรค palvelimelle, kun jotkin Forgejo-tapahtumat kรคynnistyvรคt. Lue lisรครค webkoukkujen oppaasta. @@ -2102,8 +2154,8 @@ dashboard.operation_switch=Vaihda dashboard.operation_run=Suorita dashboard.delete_inactive_accounts=Poista kaikki aktivoimattomat kรคyttรคjรคt dashboard.delete_repo_archives=Poista kaikki repojen arkistot (ZIP, TAR.GZ, jne..) -dashboard.server_uptime=Palvelimen Uptime -dashboard.current_goroutine=Nykyiset Goroutinet +dashboard.server_uptime=Palvelimen uptime +dashboard.current_goroutine=Nykyiset goroutinet dashboard.current_memory_usage=Nykyinen muistinkรคyttรถ dashboard.total_memory_allocated=Yhteensรค muistia varattu dashboard.memory_obtained=Muistia saatu @@ -2279,7 +2331,7 @@ config.default_visibility_organization=Uuden organisaation oletusnรคkyvyys config.webhook_config=Webkoukkujen asetukset config.queue_length=Jonon pituus -config.deliver_timeout=Toimitus aikakatkaisu +config.deliver_timeout=Toimituksen aikakatkaisu config.mailer_enabled=Kรคytรถssรค config.mailer_name=Nimi @@ -2426,6 +2478,8 @@ dashboard.task.unknown = Tuntematon tehtรคvรค: %[1]s dashboard.cron.error = Virhe Cronissa: %s: %[3]s dashboard.task.started = Kรคynnistetty tehtรคvรค: %[1]s dashboard.cron.finished = Cron: %[1]s on valmistunut +dashboard.resync_all_sshkeys = Pรคivitรค ".ssh/authorized_keys"-tiedosto Forgejo:n SSH-avaimilla. +dashboard.cleanup_packages = Siivoa vanhentuneet paketit [action] @@ -2601,6 +2655,15 @@ settings.link = Linkitรค tรคmรค paketti repositorioon maven.download = Lataa riippuvuus suorittamalla komentorivillรค: registry.documentation = Lisรคtietoja %s-rekisteristรค on dokumentaatiossa. owner.settings.chef.keypair.description = Avainpari vaaditaan Chef-rekisteriin tunnistautumista varten. Jos olet luonut avainparin aiemmin, uuden avainparin luominen hylkรครค aiemman avainparin. +owner.settings.cleanuprules.keep.pattern = Sรคilytรค kaavaa vastaavat versiot +owner.settings.cleanuprules.pattern_full_match = Toteuta kaavio paketin koko nimeen +owner.settings.cleanuprules.keep.title = Nรคitรค sรครคntรถjรค vastaavat versiot sรคilytetรครคn, vaikka ne vastaisivat alla olevaa poistosรครคntรถรค. +owner.settings.cleanuprules.keep.count = Sรคilytรค viimeisimmรคt +owner.settings.cleanuprules.remove.pattern = Poista kaavaa vastaavat versiot +owner.settings.cleanuprules.keep.pattern.container = Viimeisin (latest) versio sรคilytetรครคn aina Container-paketeista. +owner.settings.cleanuprules.remove.title = Nรคitรค sรครคntรถjรค vastaavat versiot poistetaan, ellei sรครคntรถ ylรคpuolella kรคske sรคilyttรครค niitรค. +owner.settings.cleanuprules.remove.days = Poista versiot, jotka ovat vanhempia kuin +arch.pacman.helper.gpg = Lisรครค luottamusvarmenne pacmanille: [secrets] creation.failed = Salaisuuden lisรครคminen epรคonnistui. diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index e45e27e7f0..8285607b1f 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -165,6 +165,7 @@ error413 = Votre quota est รฉpuisรฉ. new_repo.title = Nouveau dรฉpรดt new_migrate.link = Nouvelle migration new_org.link = Nouvelle organisation +copy_path = Copier le chemin [aria] navbar=Barre de navigation @@ -811,7 +812,7 @@ manage_emails=Gรฉrer les adresses courriels manage_themes=Thรจme par dรฉfaut manage_openid=Adresses OpenID email_desc=Votre adresse courriel principale sera utilisรฉe pour les notifications, la rรฉcupรฉration de mot de passe et, ร  condition qu'elle ne soit pas cachรฉe, les opรฉrations Git basรฉes sur le Web. -theme_desc=Ce sera votre thรจme par dรฉfaut sur le site. +theme_desc=Ce thรจme sera utilisรฉ pour l'interface web lorsque vous รชtes authentifiรฉ. primary=Principale activated=Activรฉ requires_activation=Nรฉcessite une activation @@ -1055,7 +1056,7 @@ language.localization_project = Aidez-nous ร  traduire Forgejo dans votre langue language.description = Cette langue sera enregistrรฉe dans votre compte et utilisรฉe comme langue par dรฉfaut aprรจs votre connexion. [repo] -new_repo_helper=Un dรฉpรดt contient tous les fichiers dโ€™un projet, ainsi que lโ€™historique de leurs modifications. Vous avez dรฉjร  รงa ailleurs ? Migrez-le ici. +new_repo_helper=Un dรฉpรดt contient tous les fichiers dโ€™un projet, ainsi que lโ€™historique de leurs modifications. Vous avez dรฉjร  รงa ailleurs ? Migrez-le ici.. owner=Propriรฉtaire owner_helper=Certaines organisations peuvent ne pas apparaรฎtre dans la liste dรฉroulante en raison d'une limite maximale du nombre de dรฉpรดts. repo_name=Nom du dรฉpรดt @@ -1095,7 +1096,7 @@ issue_labels=ร‰tiquettes issue_labels_helper=Sรฉlectionner un jeu d'รฉtiquettes license=Licence license_helper=Sรฉlectionner une licence -license_helper_desc=Une licence rรฉglemente ce que les autres peuvent ou ne peuvent pas faire avec votre code. Vous ne savez pas laquelle est la bonne pour votre projet ? Comment choisir une licence. +license_helper_desc=Une licence rรฉglemente ce que les autres peuvent ou ne peuvent pas faire avec votre code. Vous ne savez pas laquelle est la bonne pour votre projet ? Comment choisir une licence.. readme=LISEZMOI readme_helper=Choisissez un modรจle de fichier LISEZMOI readme_helper_desc=Le README est l'endroit idรฉal pour dรฉcrire votre projet et accueillir des contributeurs. @@ -2508,7 +2509,7 @@ settings.archive.error=Une erreur s'est produite lors de l'archivage du dรฉpรดt. settings.archive.error_ismirror=Vous ne pouvez pas archiver un dรฉpรดt en miroir. settings.archive.branchsettings_unavailable=Le paramรฉtrage des branches n'est pas disponible quand le dรฉpรดt est archivรฉ. settings.archive.tagsettings_unavailable=Le paramรฉtrage des รฉtiquettes n'est pas disponible si le dรฉpรดt est archivรฉ. -settings.archive.mirrors_unavailable = Les mirroirs ne sont pas disponibles si le dรฉpรดt a รฉtรฉ archivรฉ. +settings.archive.mirrors_unavailable = Les miroirs ne sont pas disponibles si le dรฉpรดt a รฉtรฉ archivรฉ. settings.unarchive.button=Dรฉsarchiver ce dรฉpรดt settings.unarchive.header=Rรฉhabiliter ce dรฉpรดt settings.unarchive.text=Rรฉhabiliter un dรฉpรดt dรฉgรจle les actions de rรฉvisions et de soumissions, la gestion des tickets et des demandes d'ajouts. @@ -2844,6 +2845,10 @@ diff.git-notes.remove-body = Cette note sera supprimรฉe. diff.git-notes.add = Ajouter une note diff.git-notes.remove-header = Supprimer la note issues.summary_card_alt = Fiche de synthรจse d'un ticket nommรฉ "%s" dans le dรฉpรดt %s +editor.add_tmpl.filename = fichier +issues.num_reviews_one = %d revue +issues.num_reviews_few = %d revues +settings.default_update_style_desc = Style de mise ร  jour des demandes de fusion qui sont en retard par rapport ร  la branche de base. [graphs] component_loading = Chargement %s... @@ -3662,7 +3667,7 @@ alpine.registry=Configurez ce registre en ajoutant lโ€™URL dans votre fichier /etc/apk/keys/ pour vรฉrifier la signature de l'index : alpine.registry.info=Choisissez $branch et $repository dans la liste ci-dessous. alpine.install=Pour installer le paquet, exรฉcutez la commande suivante : -alpine.repository=Informations sur le Dรฉpรดt +alpine.repository=Informations sur le dรฉpรดt alpine.repository.branches=Branches alpine.repository.repositories=Dรฉpรดts alpine.repository.architectures=Architectures @@ -3682,7 +3687,7 @@ conda.install=Pour installer le paquet en utilisant Conda, exรฉcutez la commande container.details.type=Type d'image container.details.platform=Plateforme container.pull=Tirez l'image depuis un terminal : -container.digest=Empreinteย : +container.digest=Empreinte container.multi_arch=SE / Arch container.layers=Calques d'image container.labels=Labels @@ -3797,6 +3802,7 @@ arch.version.conflicts = Conflits arch.version.replaces = Remplace arch.version.backup = Sauvegarde arch.version.makedepends = Faire des dรฉpendances +container.images.title = Images [secrets] secrets=Secrets diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 397370c883..b44f3f384d 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -765,7 +765,7 @@ cancel=Atcelt language=Valoda ui=Motฤซvs hidden_comment_types=Slฤ“pjamo piebilลพu veidi -hidden_comment_types_description=ล eit atzฤซmฤ“tie piebilลพu veidi netiks attฤ“loti pieteikumu lapฤs. "Iezฤซme" atzฤซmฤ“ลกana, piemฤ“ram, noล†ems visas " pievienoja/noล†ฤ“ma
{{end}} {{if .NoteRendered}} -
+
{{svg "octicon-note" 16 "tw-mr-2"}} {{ctx.Locale.Tr "repo.diff.git-notes"}}: {{if .NoteAuthor}} @@ -277,11 +277,11 @@ {{else}} {{.NoteCommit.Author.Name}} {{end}} - {{DateUtils.TimeSince .NoteCommit.Author.When}} + {{DateUtils.TimeSince .NoteCommit.Author.When}} {{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}} -
- - +
+ +
{{template "base/footer" .}} From e14f2d0c843ca7ccc4d5247419141b123789792c Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 30 Dec 2024 00:09:07 +0100 Subject: [PATCH 0039/1052] feat: don't allow blocking the doer - In the case of organization blocking users, disallow blocking the doer. - Resolves #5390 - Added integration test. --- options/locale/locale_en-US.ini | 1 + routers/web/org/setting/blocked_users.go | 6 ++++++ tests/integration/block_test.go | 14 ++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5a89662cb2..216ea03ddf 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1042,6 +1042,7 @@ visibility.private_tooltip = Visible only to members of organizations you have j blocked_since = Blocked since %s user_unblock_success = The user has been unblocked successfully. user_block_success = The user has been blocked successfully. +user_block_yourself = You cannot block yourself. [repo] rss.must_be_on_branch = You must be on a branch to have an RSS feed. diff --git a/routers/web/org/setting/blocked_users.go b/routers/web/org/setting/blocked_users.go index 0c7f245c13..2cf3f39ef4 100644 --- a/routers/web/org/setting/blocked_users.go +++ b/routers/web/org/setting/blocked_users.go @@ -53,6 +53,12 @@ func BlockedUsersBlock(ctx *context.Context) { return } + if u.ID == ctx.Doer.ID { + ctx.Flash.Error(ctx.Tr("settings.user_block_yourself")) + ctx.Redirect(ctx.Org.OrgLink + "/settings/blocked_users") + return + } + if err := user_service.BlockUser(ctx, ctx.Org.Organization.ID, u.ID); err != nil { ctx.ServerError("BlockUser", err) return diff --git a/tests/integration/block_test.go b/tests/integration/block_test.go index b17a445bf8..1fa201a0be 100644 --- a/tests/integration/block_test.go +++ b/tests/integration/block_test.go @@ -147,6 +147,20 @@ func TestBlockUserFromOrganization(t *testing.T) { session.MakeRequest(t, req, http.StatusInternalServerError) }) }) + + t.Run("Block the doer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithValues(t, "POST", org.OrganisationLink()+"/settings/blocked_users/block", map[string]string{ + "_csrf": GetCSRF(t, session, org.OrganisationLink()+"/settings/blocked_users"), + "uname": doer.Name, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + assert.False(t, unittest.BeanExists(t, &user_model.BlockedUser{BlockID: doer.ID, UserID: org.ID})) + flashCookie := session.GetCookie(forgejo_context.CookieNameFlash) + assert.NotNil(t, flashCookie) + assert.EqualValues(t, "error%3DYou%2Bcannot%2Bblock%2Byourself.", flashCookie.Value) + }) } // TestBlockActions ensures that certain actions cannot be performed as a doer From 0569714439db3b2824ce112e19da3720eaa8f9fd Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 Dec 2024 08:10:09 +0000 Subject: [PATCH 0040/1052] Update renovate Docker tag to v39.86.0 (forgejo) (#6412) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a9de57e523..cd57cfaa24 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.28.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.0 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@39.82.1 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate +RENOVATE_NPM_PACKAGE ?= renovate@39.86.0 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... From 57f7253610fd1404196b760fdd91d11d848b8d83 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 29 Dec 2024 23:50:48 +0100 Subject: [PATCH 0041/1052] fix: use DateUtils for blocked users list - Should've been fixed with #5796 but seems I've overlooked. --- templates/shared/blocked_users_list.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/shared/blocked_users_list.tmpl b/templates/shared/blocked_users_list.tmpl index 409b7f4aa5..f445c1bb10 100644 --- a/templates/shared/blocked_users_list.tmpl +++ b/templates/shared/blocked_users_list.tmpl @@ -9,7 +9,7 @@ {{template "shared/user/name" .}}
- {{ctx.Locale.Tr "settings.blocked_since" (DateTime "short" .CreatedUnix)}} + {{ctx.Locale.Tr "settings.blocked_since" (DateUtils.AbsoluteShort .CreatedUnix)}}
From c67d63d88a77f81b3f79b75642624b3457d4e8b6 Mon Sep 17 00:00:00 2001 From: Otto Richter Date: Mon, 30 Dec 2024 16:06:18 +0100 Subject: [PATCH 0042/1052] Fix issue/comment menus Closes https://codeberg.org/forgejo/forgejo/issues/1120 - Adds labels to reaction and context menu. - Fixes taborder in markdown combobox buttons. They are now only one "tab" stop and can be navigated with arrow buttons and in the right order (previously, it would skip the table button). - Generates more verbose output for the reactio selectors to provide content for users who cannot identify the meaning of these buttons visually. Explicit aria-labels are now preferred over auto-generated ones. --- options/locale/locale_en-US.ini | 6 ++++++ .../repo/issue/view_content/add_reaction.tmpl | 2 +- .../repo/issue/view_content/context_menu.tmpl | 2 +- templates/repo/issue/view_content/reactions.tmpl | 15 ++++++++++++--- templates/shared/combomarkdowneditor.tmpl | 4 ++-- tests/e2e/issue-comment.test.e2e.ts | 13 +++++++++++++ web_src/js/modules/tippy.js | 5 ++++- 7 files changed, 39 insertions(+), 8 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 216ea03ddf..17f5e4dcb6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1634,6 +1634,12 @@ issues.num_reviews_one = %d review issues.num_reviews_few = %d reviews issues.commented_at = `commented %s` issues.delete_comment_confirm = Are you sure you want to delete this comment? +issues.reaction.add = Add reaction +issues.reaction.alt_few = %[1]s reacted %[2]s. +issues.reaction.alt_many = %[1]s and %[2]d more reacted %[3]s. +issues.reaction.alt_remove = Remove %[1]s reaction from comment. +issues.reaction.alt_add = Add %[1]s reaction to comment. +issues.context.menu = Comment menu issues.context.copy_link = Copy link issues.context.quote_reply = Quote reply issues.context.reference_issue = Reference in a new issue diff --git a/templates/repo/issue/view_content/add_reaction.tmpl b/templates/repo/issue/view_content/add_reaction.tmpl index 37931f287e..5d4aa2298e 100644 --- a/templates/repo/issue/view_content/add_reaction.tmpl +++ b/templates/repo/issue/view_content/add_reaction.tmpl @@ -1,5 +1,5 @@ {{if .ctxData.IsSigned}} -