diff --git a/.golangci.yml b/.golangci.yml
index 70efd288ff..2ad39fbae2 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -50,6 +50,8 @@ linters:
require-explanation: true
require-specific: true
gocritic:
+ enabled-checks:
+ - equalFold
disabled-checks:
- ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 979831eb9b..6a7126388e 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -30,7 +30,7 @@ These are the values to which people in the Gitea community should aspire.
- **Be constructive.**
- Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation.
- Avoid unconstructive criticism: don't merely decry the current state of affairs; offer—or at least solicit—suggestions as to how things may be improved.
- - Avoid snarking (pithy, unproductive, sniping comments)
+ - Avoid snarking (pithy, unproductive, sniping comments).
- Avoid discussing potentially offensive or sensitive issues; this all too often leads to unnecessary conflict.
- Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults to a person or group).
- **Be responsible.**
@@ -42,7 +42,7 @@ People are complicated. You should expect to be misunderstood and to misundersta
### Our Pledge
-In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
### Our Standards
diff --git a/README.md b/README.md
index 017ca629d0..aa2c6010fa 100644
--- a/README.md
+++ b/README.md
@@ -80,9 +80,9 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request
[](https://translate.gitea.com)
-Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
+Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language, ask one of the managers in the Crowdin project to add a new language there.
-You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up.
+You can also just create an issue for adding a language or ask on Discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty, but we hope to fill it as questions pop up.
Get more information from [documentation](https://docs.gitea.com/contributing/localization).
diff --git a/models/repo/language_stats.go b/models/repo/language_stats.go
index 0bc0f1fb40..7db8cd4dd2 100644
--- a/models/repo/language_stats.go
+++ b/models/repo/language_stats.go
@@ -157,18 +157,17 @@ func UpdateLanguageStats(ctx context.Context, repo *Repository, commitID string,
for lang, size := range stats {
if size > s {
s = size
- topLang = strings.ToLower(lang)
+ topLang = lang
}
}
for lang, size := range stats {
upd := false
- llang := strings.ToLower(lang)
for _, s := range oldstats {
// Update already existing language
- if strings.ToLower(s.Language) == llang {
+ if strings.EqualFold(s.Language, lang) {
s.CommitID = commitID
- s.IsPrimary = llang == topLang
+ s.IsPrimary = lang == topLang
s.Size = size
if _, err := sess.ID(s.ID).Cols("`commit_id`", "`size`", "`is_primary`").Update(s); err != nil {
return err
@@ -182,7 +181,7 @@ func UpdateLanguageStats(ctx context.Context, repo *Repository, commitID string,
if err := db.Insert(ctx, &LanguageStat{
RepoID: repo.ID,
CommitID: commitID,
- IsPrimary: llang == topLang,
+ IsPrimary: lang == topLang,
Language: lang,
Size: size,
}); err != nil {
diff --git a/modules/markup/mdstripper/mdstripper.go b/modules/markup/mdstripper/mdstripper.go
index 6e392444b4..5a6504416a 100644
--- a/modules/markup/mdstripper/mdstripper.go
+++ b/modules/markup/mdstripper/mdstripper.go
@@ -91,8 +91,7 @@ func (r *stripRenderer) processAutoLink(w io.Writer, link []byte) {
}
// Note: we're not attempting to match the URL scheme (http/https)
- host := strings.ToLower(u.Host)
- if host != "" && host != strings.ToLower(r.localhost.Host) {
+ if u.Host != "" && !strings.EqualFold(u.Host, r.localhost.Host) {
// Process out of band
r.links = append(r.links, linkStr)
return
diff --git a/modules/packages/pub/metadata.go b/modules/packages/pub/metadata.go
index afb464e462..9b00472eb2 100644
--- a/modules/packages/pub/metadata.go
+++ b/modules/packages/pub/metadata.go
@@ -88,7 +88,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
if err != nil {
return nil, err
}
- } else if strings.ToLower(hd.Name) == "readme.md" {
+ } else if strings.EqualFold(hd.Name, "readme.md") {
data, err := io.ReadAll(tr)
if err != nil {
return nil, err
diff --git a/modules/setting/actions.go b/modules/setting/actions.go
index 913872eaf2..8bace1f750 100644
--- a/modules/setting/actions.go
+++ b/modules/setting/actions.go
@@ -62,11 +62,11 @@ func (c logCompression) IsValid() bool {
}
func (c logCompression) IsNone() bool {
- return strings.ToLower(string(c)) == "none"
+ return string(c) == "none"
}
func (c logCompression) IsZstd() bool {
- return c == "" || strings.ToLower(string(c)) == "zstd"
+ return c == "" || string(c) == "zstd"
}
func loadActionsFrom(rootCfg ConfigProvider) error {
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index ff3f7cfda1..e454bce4bd 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -40,7 +40,6 @@ func NewFuncMap() template.FuncMap {
"HTMLFormat": htmlFormat,
"QueryEscape": queryEscape,
"QueryBuild": QueryBuild,
- "JSEscape": jsEscapeSafe,
"SanitizeHTML": SanitizeHTML,
"URLJoin": util.URLJoin,
"DotEscape": dotEscape,
@@ -181,10 +180,6 @@ func htmlFormat(s any, args ...any) template.HTML {
panic(fmt.Sprintf("unexpected type %T", s))
}
-func jsEscapeSafe(s string) template.HTML {
- return template.HTML(template.JSEscapeString(s))
-}
-
func queryEscape(s string) template.URL {
return template.URL(url.QueryEscape(s))
}
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index 81f8235bd2..7e3a952e7b 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -57,10 +57,6 @@ func TestSubjectBodySeparator(t *testing.T) {
"Insufficient\n--\nSeparators")
}
-func TestJSEscapeSafe(t *testing.T) {
- assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, jsEscapeSafe(`&<>'"`))
-}
-
func TestSanitizeHTML(t *testing.T) {
assert.Equal(t, template.HTML(`link xss
inline
`), SanitizeHTML(`link xss inline
`))
}
diff --git a/modules/util/slice.go b/modules/util/slice.go
index da6886491e..aaa729c1c9 100644
--- a/modules/util/slice.go
+++ b/modules/util/slice.go
@@ -12,8 +12,7 @@ import (
// SliceContainsString sequential searches if string exists in slice.
func SliceContainsString(slice []string, target string, insensitive ...bool) bool {
if len(insensitive) != 0 && insensitive[0] {
- target = strings.ToLower(target)
- return slices.ContainsFunc(slice, func(t string) bool { return strings.ToLower(t) == target })
+ return slices.ContainsFunc(slice, func(t string) bool { return strings.EqualFold(t, target) })
}
return slices.Contains(slice, target)
diff --git a/modules/util/time_str.go b/modules/util/time_str.go
index 0fccfe82cc..81b132c3db 100644
--- a/modules/util/time_str.go
+++ b/modules/util/time_str.go
@@ -59,7 +59,7 @@ func TimeEstimateParse(timeStr string) (int64, error) {
unit := timeStr[match[4]:match[5]]
found := false
for _, u := range timeStrGlobalVars().units {
- if strings.ToLower(unit) == u.name {
+ if strings.EqualFold(unit, u.name) {
total += amount * u.num
found = true
break
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index bd71d58d87..612e831dad 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2355,6 +2355,7 @@ settings.payload_url = Target URL
settings.http_method = HTTP Method
settings.content_type = POST Content Type
settings.secret = Secret
+settings.webhook_secret_desc = If the webhook server supports using secret, you can follow the webhook's manual and fill in a secret here.
settings.slack_username = Username
settings.slack_icon_url = Icon URL
settings.slack_color = Color
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 4a4bf12657..f412e8a06c 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -145,7 +145,7 @@ func repoAssignment() func(ctx *context.APIContext) {
)
// Check if the user is the same as the repository owner.
- if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
+ if ctx.IsSigned && strings.EqualFold(ctx.Doer.LowerName, userName) {
owner = ctx.Doer
} else {
owner, err = user_model.GetUserByName(ctx, userName)
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index c2c10cc695..eed9c19fe1 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -276,7 +276,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
collaboratorUsername := ctx.PathParam("collaborator")
- if !ctx.Doer.IsAdmin && ctx.Doer.LowerName != strings.ToLower(collaboratorUsername) && !ctx.IsUserRepoAdmin() {
+ if !ctx.Doer.IsAdmin && !strings.EqualFold(ctx.Doer.LowerName, collaboratorUsername) && !ctx.IsUserRepoAdmin() {
ctx.APIError(http.StatusForbidden, "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
return
}
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 8acc912796..292b267c01 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -669,7 +669,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
newRepoName = *opts.Name
}
// Check if repository name has been changed and not just a case change
- if repo.LowerName != strings.ToLower(newRepoName) {
+ if !strings.EqualFold(repo.LowerName, newRepoName) {
if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil {
switch {
case repo_model.IsErrRepoAlreadyExist(err):
diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go
index dc9f34fd44..79989d8fbe 100644
--- a/routers/web/auth/oauth2_provider.go
+++ b/routers/web/auth/oauth2_provider.go
@@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"strconv"
+ "strings"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
@@ -161,9 +162,7 @@ func IntrospectOAuth(ctx *context.Context) {
if err == nil && app != nil {
response.Active = true
response.Scope = grant.Scope
- response.Issuer = setting.AppURL
- response.Audience = []string{app.ClientID}
- response.Subject = strconv.FormatInt(grant.UserID, 10)
+ response.RegisteredClaims = oauth2_provider.NewJwtRegisteredClaimsFromUser(app.ClientID, grant.UserID, nil /*exp*/)
}
if user, err := user_model.GetUserByID(ctx, grant.UserID); err == nil {
response.Username = user.Name
@@ -423,7 +422,14 @@ func GrantApplicationOAuth(ctx *context.Context) {
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
func OIDCWellKnown(ctx *context.Context) {
- ctx.Data["SigningKey"] = oauth2_provider.DefaultSigningKey
+ if !setting.OAuth2.Enabled {
+ http.NotFound(ctx.Resp, ctx.Req)
+ return
+ }
+ jwtRegisteredClaims := oauth2_provider.NewJwtRegisteredClaimsFromUser("well-known", 0, nil)
+ ctx.Data["OidcIssuer"] = jwtRegisteredClaims.Issuer // use the consistent issuer from the JWT registered claims
+ ctx.Data["OidcBaseUrl"] = strings.TrimSuffix(setting.AppURL, "/")
+ ctx.Data["SigningKeyMethodAlg"] = oauth2_provider.DefaultSigningKey.SigningMethod().Alg()
ctx.JSONTemplate("user/auth/oidc_wellknown")
}
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 7e1b923fa4..52b2e9995e 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -249,7 +249,7 @@ func ViewPost(ctx *context_module.Context) {
ID: v.ID,
Name: v.Name,
Status: v.Status.String(),
- CanRerun: v.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions),
+ CanRerun: resp.State.Run.CanRerun,
Duration: v.Duration().String(),
})
}
@@ -445,7 +445,7 @@ func Rerun(ctx *context_module.Context) {
return
}
}
- ctx.JSON(http.StatusOK, struct{}{})
+ ctx.JSONOK()
return
}
@@ -460,12 +460,12 @@ func Rerun(ctx *context_module.Context) {
}
}
- ctx.JSON(http.StatusOK, struct{}{})
+ ctx.JSONOK()
}
func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shouldBlock bool) error {
status := job.Status
- if !status.IsDone() {
+ if !status.IsDone() || !job.Run.Status.IsDone() {
return nil
}
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index 6e16ead183..0865d9d7c0 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -165,7 +165,7 @@ func handleSettingsPostUpdate(ctx *context.Context) {
newRepoName := form.RepoName
// Check if repository name has been changed.
- if repo.LowerName != strings.ToLower(newRepoName) {
+ if !strings.EqualFold(repo.LowerName, newRepoName) {
// Close the GitRepo if open
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index 006abafe57..f107449749 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -198,7 +198,6 @@ type webhookParams struct {
URL string
ContentType webhook.HookContentType
- Secret string
HTTPMethod string
WebhookForm forms.WebhookForm
Meta any
@@ -237,7 +236,7 @@ func createWebhook(ctx *context.Context, params webhookParams) {
URL: params.URL,
HTTPMethod: params.HTTPMethod,
ContentType: params.ContentType,
- Secret: params.Secret,
+ Secret: params.WebhookForm.Secret,
HookEvent: ParseHookEvent(params.WebhookForm),
IsActive: params.WebhookForm.Active,
Type: params.Type,
@@ -290,7 +289,7 @@ func editWebhook(ctx *context.Context, params webhookParams) {
w.URL = params.URL
w.ContentType = params.ContentType
- w.Secret = params.Secret
+ w.Secret = params.WebhookForm.Secret
w.HookEvent = ParseHookEvent(params.WebhookForm)
w.IsActive = params.WebhookForm.Active
w.HTTPMethod = params.HTTPMethod
@@ -336,7 +335,6 @@ func giteaHookParams(ctx *context.Context) webhookParams {
Type: webhook_module.GITEA,
URL: form.PayloadURL,
ContentType: contentType,
- Secret: form.Secret,
HTTPMethod: form.HTTPMethod,
WebhookForm: form.WebhookForm,
}
@@ -364,7 +362,6 @@ func gogsHookParams(ctx *context.Context) webhookParams {
Type: webhook_module.GOGS,
URL: form.PayloadURL,
ContentType: contentType,
- Secret: form.Secret,
WebhookForm: form.WebhookForm,
}
}
diff --git a/routers/web/swagger_json.go b/routers/web/swagger_json.go
index fc39b504a9..52f6beaf59 100644
--- a/routers/web/swagger_json.go
+++ b/routers/web/swagger_json.go
@@ -4,10 +4,15 @@
package web
import (
+ "html/template"
+
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
)
// SwaggerV1Json render swagger v1 json
func SwaggerV1Json(ctx *context.Context) {
+ ctx.Data["SwaggerAppVer"] = template.HTML(template.JSEscapeString(setting.AppVer))
+ ctx.Data["SwaggerAppSubUrl"] = setting.AppSubURL // it is JS-safe
ctx.JSONTemplate("swagger/v1_json")
}
diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go
index e6bce04a83..f6c032492f 100644
--- a/services/auth/source/ldap/source_search.go
+++ b/services/auth/source/ldap/source_search.go
@@ -241,7 +241,7 @@ func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string, applyGr
}
func (source *Source) getUserAttributeListedInGroup(entry *ldap.Entry) string {
- if strings.ToLower(source.UserUID) == "dn" {
+ if strings.EqualFold(source.UserUID, "dn") {
return entry.DN
}
diff --git a/services/context/context_response.go b/services/context/context_response.go
index 4e11e29b69..3f64fc7352 100644
--- a/services/context/context_response.go
+++ b/services/context/context_response.go
@@ -92,7 +92,7 @@ func (ctx *Context) HTML(status int, name templates.TplName) {
}
// JSONTemplate renders the template as JSON response
-// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
+// keep in mind that the template is processed in HTML context, so JSON things should be handled carefully, e.g.: use JSEscape
func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
if err != nil {
diff --git a/services/context/org.go b/services/context/org.go
index c8b6ed09b7..1cd8923178 100644
--- a/services/context/org.go
+++ b/services/context/org.go
@@ -208,7 +208,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) {
if len(teamName) > 0 {
teamExists := false
for _, team := range ctx.Org.Teams {
- if team.LowerName == strings.ToLower(teamName) {
+ if strings.EqualFold(team.LowerName, teamName) {
teamExists = true
ctx.Org.Team = team
ctx.Org.IsTeamMember = true
diff --git a/services/context/repo.go b/services/context/repo.go
index 572211712b..afc6de9b16 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -429,7 +429,7 @@ func RepoAssignment(ctx *Context) {
}
// Check if the user is the same as the repository owner
- if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
+ if ctx.IsSigned && strings.EqualFold(ctx.Doer.LowerName, userName) {
ctx.Repo.Owner = ctx.Doer
} else {
ctx.Repo.Owner, err = user_model.GetUserByName(ctx, userName)
diff --git a/services/context/user.go b/services/context/user.go
index c09ded8339..f1a3035ee9 100644
--- a/services/context/user.go
+++ b/services/context/user.go
@@ -61,7 +61,7 @@ func UserAssignmentAPI() func(ctx *APIContext) {
func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, any)) (contextUser *user_model.User) {
username := ctx.PathParam("username")
- if doer != nil && doer.LowerName == strings.ToLower(username) {
+ if doer != nil && strings.EqualFold(doer.LowerName, username) {
contextUser = doer
} else {
var err error
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index d116bb9f11..cb267f891c 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -238,6 +238,7 @@ type WebhookForm struct {
Active bool
BranchFilter string `binding:"GlobPattern"`
AuthorizationHeader string
+ Secret string
}
// PushOnly if the hook will be triggered when push
@@ -260,7 +261,6 @@ type NewWebhookForm struct {
PayloadURL string `binding:"Required;ValidUrl"`
HTTPMethod string `binding:"Required;In(POST,GET)"`
ContentType int `binding:"Required"`
- Secret string
WebhookForm
}
@@ -274,7 +274,6 @@ func (f *NewWebhookForm) Validate(req *http.Request, errs binding.Errors) bindin
type NewGogshookForm struct {
PayloadURL string `binding:"Required;ValidUrl"`
ContentType int `binding:"Required"`
- Secret string
WebhookForm
}
diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go
index e01777031b..5a190d8616 100644
--- a/services/oauth2_provider/access_token.go
+++ b/services/oauth2_provider/access_token.go
@@ -106,6 +106,20 @@ func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope {
return auth.AccessTokenScopeAll
}
+func NewJwtRegisteredClaimsFromUser(clientID string, grantUserID int64, exp *jwt.NumericDate) jwt.RegisteredClaims {
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+ // The issuer value returned MUST be identical to the Issuer URL that was used as the prefix to /.well-known/openid-configuration
+ // to retrieve the configuration information. This MUST also be identical to the "iss" Claim value in ID Tokens issued from this Issuer.
+ // * https://accounts.google.com/.well-known/openid-configuration
+ // * https://github.com/login/oauth/.well-known/openid-configuration
+ return jwt.RegisteredClaims{
+ Issuer: strings.TrimSuffix(setting.AppURL, "/"),
+ Audience: []string{clientID},
+ Subject: strconv.FormatInt(grantUserID, 10),
+ ExpiresAt: exp,
+ }
+}
+
func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, serverKey, clientKey JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
if setting.OAuth2.InvalidateRefreshTokens {
if err := grant.IncreaseCounter(ctx); err != nil {
@@ -176,13 +190,8 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
}
idToken := &OIDCToken{
- RegisteredClaims: jwt.RegisteredClaims{
- ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()),
- Issuer: setting.AppURL,
- Audience: []string{app.ClientID},
- Subject: strconv.FormatInt(grant.UserID, 10),
- },
- Nonce: grant.Nonce,
+ RegisteredClaims: NewJwtRegisteredClaimsFromUser(app.ClientID, grant.UserID, jwt.NewNumericDate(expirationDate.AsTime())),
+ Nonce: grant.Nonce,
}
if grant.ScopeContains("profile") {
idToken.Name = user.DisplayName()
diff --git a/services/repository/files/file.go b/services/repository/files/file.go
index 13d171d139..f48e32b427 100644
--- a/services/repository/files/file.go
+++ b/services/repository/files/file.go
@@ -144,7 +144,7 @@ func CleanGitTreePath(name string) string {
name = util.PathJoinRel(name)
// Git disallows any filenames to have a .git directory in them.
for part := range strings.SplitSeq(name, "/") {
- if strings.ToLower(part) == ".git" {
+ if strings.EqualFold(part, ".git") {
return ""
}
}
diff --git a/templates/admin/hooks.tmpl b/templates/admin/hooks.tmpl
index c77d27dbd0..d5fdef6850 100644
--- a/templates/admin/hooks.tmpl
+++ b/templates/admin/hooks.tmpl
@@ -1,9 +1,6 @@
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin hooks")}}
-
{{template "repo/settings/webhook/base_list" .SystemWebhooks}}
{{template "repo/settings/webhook/base_list" .DefaultWebhooks}}
-
- {{template "repo/settings/webhook/delete_modal" .}}
{{template "admin/layout_footer" .}}
diff --git a/templates/org/settings/hooks.tmpl b/templates/org/settings/hooks.tmpl
index 9f307968f8..b05e22fe20 100644
--- a/templates/org/settings/hooks.tmpl
+++ b/templates/org/settings/hooks.tmpl
@@ -1,5 +1,5 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings webhooks")}}
- {{template "repo/settings/webhook/list" .}}
+ {{template "repo/settings/webhook/base_list" .}}
{{template "org/settings/layout_footer" .}}
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl
index 6aa776da02..692808a32d 100644
--- a/templates/projects/view.tmpl
+++ b/templates/projects/view.tmpl
@@ -134,7 +134,7 @@
- {{template "repo/settings/webhook/settings" .}}
+ {{/* FIXME: support authorization header or not? */}}
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" "optional"}}
{{end}}
diff --git a/templates/repo/settings/webhook/feishu.tmpl b/templates/repo/settings/webhook/feishu.tmpl
index d80deab26f..13bd0d92a1 100644
--- a/templates/repo/settings/webhook/feishu.tmpl
+++ b/templates/repo/settings/webhook/feishu.tmpl
@@ -1,12 +1,14 @@
{{if eq .HookType "feishu"}}
- {{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu")}}
- {{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite")}}
+
+ {{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu")}}
+ {{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite")}}
+
{{end}}
diff --git a/templates/repo/settings/webhook/gitea.tmpl b/templates/repo/settings/webhook/gitea.tmpl
index e6eb61ea92..30f14d609b 100644
--- a/templates/repo/settings/webhook/gitea.tmpl
+++ b/templates/repo/settings/webhook/gitea.tmpl
@@ -31,10 +31,11 @@
-
- {{ctx.Locale.Tr "repo.settings.secret"}}
-
-
- {{template "repo/settings/webhook/settings" .}}
+ {{template "repo/settings/webhook/settings" dict
+ "BaseLink" .BaseLink
+ "Webhook" .Webhook
+ "UseAuthorizationHeader" "optional"
+ "UseRequestSecret" "optional"
+ }}
{{end}}
diff --git a/templates/repo/settings/webhook/gogs.tmpl b/templates/repo/settings/webhook/gogs.tmpl
index e91a3279e4..c0e054602a 100644
--- a/templates/repo/settings/webhook/gogs.tmpl
+++ b/templates/repo/settings/webhook/gogs.tmpl
@@ -19,10 +19,11 @@
-
- {{ctx.Locale.Tr "repo.settings.secret"}}
-
-
- {{template "repo/settings/webhook/settings" .}}
+ {{template "repo/settings/webhook/settings" dict
+ "BaseLink" .BaseLink
+ "Webhook" .Webhook
+ "UseAuthorizationHeader" "optional"
+ "UseRequestSecret" "optional"
+ }}
{{end}}
diff --git a/templates/repo/settings/webhook/list.tmpl b/templates/repo/settings/webhook/list.tmpl
deleted file mode 100644
index b24159fccb..0000000000
--- a/templates/repo/settings/webhook/list.tmpl
+++ /dev/null
@@ -1,4 +0,0 @@
-
-{{template "repo/settings/webhook/base_list" .}}
-
-{{template "repo/settings/webhook/delete_modal" .}}
diff --git a/templates/repo/settings/webhook/matrix.tmpl b/templates/repo/settings/webhook/matrix.tmpl
index 7f1c9f08e6..e0aad2d807 100644
--- a/templates/repo/settings/webhook/matrix.tmpl
+++ b/templates/repo/settings/webhook/matrix.tmpl
@@ -22,6 +22,6 @@
- {{template "repo/settings/webhook/settings" .}}
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" "required"}}
{{end}}
diff --git a/templates/repo/settings/webhook/msteams.tmpl b/templates/repo/settings/webhook/msteams.tmpl
index 62ea24e763..17718a1064 100644
--- a/templates/repo/settings/webhook/msteams.tmpl
+++ b/templates/repo/settings/webhook/msteams.tmpl
@@ -6,6 +6,7 @@
{{ctx.Locale.Tr "repo.settings.payload_url"}}
- {{template "repo/settings/webhook/settings" .}}
+ {{/* FIXME: support authorization header or not? */}}
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" "optional"}}
{{end}}
diff --git a/templates/repo/settings/webhook/packagist.tmpl b/templates/repo/settings/webhook/packagist.tmpl
index 25aba2a435..c813e7c2af 100644
--- a/templates/repo/settings/webhook/packagist.tmpl
+++ b/templates/repo/settings/webhook/packagist.tmpl
@@ -14,6 +14,7 @@
{{ctx.Locale.Tr "repo.settings.packagist_package_url"}}
- {{template "repo/settings/webhook/settings" .}}
+ {{/* FIXME: support authorization header or not? */}}
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" "optional"}}
{{end}}
diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl
index a330448c9e..a8ad1d6c9e 100644
--- a/templates/repo/settings/webhook/settings.tmpl
+++ b/templates/repo/settings/webhook/settings.tmpl
@@ -1,4 +1,52 @@
-{{$isNew:=or .PageIsSettingsHooksNew .PageIsAdminDefaultHooksNew .PageIsAdminSystemHooksNew}}
+{{/* Template attributes:
+- BaseLink: Base URL for the repository settings
+- WebHook: Webhook object containing details about the webhook
+- UseAuthorizationHeader: optional or required
+- UseRequestSecret: optional or required
+*/}}
+{{$isNew := not .Webhook.ID}}
+
+
+
+
+ {{ctx.Locale.Tr "repo.settings.active"}}
+ {{ctx.Locale.Tr "repo.settings.active_helper"}}
+
+
+
+
+{{if .UseAuthorizationHeader}}
+ {{$attributeValid := or (eq .UseAuthorizationHeader "optional") (eq .UseAuthorizationHeader "required")}}
+ {{if not $attributeValid}}Invalid UseAuthorizationHeader: {{.UseAuthorizationHeader}}}
{{end}}
+ {{$required := eq .UseAuthorizationHeader "required"}}
+
+ {{ctx.Locale.Tr "repo.settings.authorization_header"}}
+
+ {{if not $required}}
+ {{ctx.Locale.Tr "repo.settings.authorization_header_desc" (HTMLFormat "%s
, %s
" "Bearer token123456" "Basic YWxhZGRpbjpvcGVuc2VzYW1l")}}
+ {{end}}
+
+{{end}}
+
+
+{{if .UseRequestSecret}}
+ {{$attributeValid := or (eq .UseRequestSecret "optional") (eq .UseRequestSecret "required")}}
+ {{if not $attributeValid}}Invalid UseRequestSecret: {{.UseRequestSecret}}}
{{end}}
+ {{$required := eq .UseRequestSecret "required"}}
+
+ {{ctx.Locale.Tr "repo.settings.secret"}}
+
+ {{ctx.Locale.Tr "repo.settings.webhook_secret_desc"}}
+
+{{end}}
+
+
+
+ {{ctx.Locale.Tr "repo.settings.branch_filter"}}
+
+ {{ctx.Locale.Tr "repo.settings.branch_filter_desc" "https://pkg.go.dev/github.com/gobwas/glob#Compile" "github.com/gobwas/glob"}}
+
+
{{ctx.Locale.Tr "repo.settings.event_desc"}}
@@ -286,38 +334,14 @@
-
-
- {{ctx.Locale.Tr "repo.settings.branch_filter"}}
-
- {{ctx.Locale.Tr "repo.settings.branch_filter_desc" "https://pkg.go.dev/github.com/gobwas/glob#Compile" "github.com/gobwas/glob"}}
-
-
-
-
- {{ctx.Locale.Tr "repo.settings.authorization_header"}}
-
- {{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}}
- {{ctx.Locale.Tr "repo.settings.authorization_header_desc" (HTMLFormat "%s
, %s
" "Bearer token123456" "Basic YWxhZGRpbjpvcGVuc2VzYW1l")}}
- {{end}}
-
-
-
-
-
-
-
- {{ctx.Locale.Tr "repo.settings.active"}}
- {{ctx.Locale.Tr "repo.settings.active_helper"}}
-
-
-
-{{template "repo/settings/webhook/delete_modal" .}}
diff --git a/templates/repo/settings/webhook/slack.tmpl b/templates/repo/settings/webhook/slack.tmpl
index e7cae92d4b..519d6afa1a 100644
--- a/templates/repo/settings/webhook/slack.tmpl
+++ b/templates/repo/settings/webhook/slack.tmpl
@@ -23,6 +23,7 @@
{{ctx.Locale.Tr "repo.settings.slack_color"}}
- {{template "repo/settings/webhook/settings" .}}
+ {{/* FIXME: support authorization header or not? */}}
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" "optional"}}
{{end}}
diff --git a/templates/repo/settings/webhook/telegram.tmpl b/templates/repo/settings/webhook/telegram.tmpl
index f92c2be0db..5ab89b72cc 100644
--- a/templates/repo/settings/webhook/telegram.tmpl
+++ b/templates/repo/settings/webhook/telegram.tmpl
@@ -14,6 +14,7 @@
{{ctx.Locale.Tr "repo.settings.thread_id"}}
- {{template "repo/settings/webhook/settings" .}}
+ {{/* FIXME: support authorization header or not? */}}
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" "optional"}}
{{end}}
diff --git a/templates/repo/settings/webhook/wechatwork.tmpl b/templates/repo/settings/webhook/wechatwork.tmpl
index 78a1617123..cbc29b4610 100644
--- a/templates/repo/settings/webhook/wechatwork.tmpl
+++ b/templates/repo/settings/webhook/wechatwork.tmpl
@@ -6,6 +6,7 @@
{{ctx.Locale.Tr "repo.settings.payload_url"}}
- {{template "repo/settings/webhook/settings" .}}
+ {{/* FIXME: support authorization header or not? */}}
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" "optional"}}
{{end}}
diff --git a/templates/swagger/v1_input.json b/templates/swagger/v1_input.json
index 1979febebb..e74c8fc9c4 100644
--- a/templates/swagger/v1_input.json
+++ b/templates/swagger/v1_input.json
@@ -1,6 +1,6 @@
{
"info": {
- "version": "{{AppVer | JSEscape}}"
+ "version": "{{.SwaggerAppVer}}"
},
- "basePath": "{{AppSubUrl | JSEscape}}/api/v1"
+ "basePath": "{{.SwaggerAppSubUrl}}/api/v1"
}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 879f59df2e..1cc3735b2b 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -19,9 +19,9 @@
"name": "MIT",
"url": "http://opensource.org/licenses/MIT"
},
- "version": "{{AppVer | JSEscape}}"
+ "version": "{{.SwaggerAppVer}}"
},
- "basePath": "{{AppSubUrl | JSEscape}}/api/v1",
+ "basePath": "{{.SwaggerAppSubUrl}}/api/v1",
"paths": {
"/activitypub/user-id/{user-id}": {
"get": {
diff --git a/templates/user/auth/oidc_wellknown.tmpl b/templates/user/auth/oidc_wellknown.tmpl
index 54bb4a763d..52c9a1b788 100644
--- a/templates/user/auth/oidc_wellknown.tmpl
+++ b/templates/user/auth/oidc_wellknown.tmpl
@@ -1,16 +1,16 @@
{
- "issuer": "{{AppUrl | JSEscape}}",
- "authorization_endpoint": "{{AppUrl | JSEscape}}login/oauth/authorize",
- "token_endpoint": "{{AppUrl | JSEscape}}login/oauth/access_token",
- "jwks_uri": "{{AppUrl | JSEscape}}login/oauth/keys",
- "userinfo_endpoint": "{{AppUrl | JSEscape}}login/oauth/userinfo",
- "introspection_endpoint": "{{AppUrl | JSEscape}}login/oauth/introspect",
+ "issuer": "{{.OidcIssuer}}",
+ "authorization_endpoint": "{{.OidcBaseUrl}}/login/oauth/authorize",
+ "token_endpoint": "{{.OidcBaseUrl}}/login/oauth/access_token",
+ "jwks_uri": "{{.OidcBaseUrl}}/login/oauth/keys",
+ "userinfo_endpoint": "{{.OidcBaseUrl}}/login/oauth/userinfo",
+ "introspection_endpoint": "{{.OidcBaseUrl}}/login/oauth/introspect",
"response_types_supported": [
"code",
"id_token"
],
"id_token_signing_alg_values_supported": [
- "{{.SigningKey.SigningMethod.Alg | JSEscape}}"
+ "{{.SigningKeyMethodAlg}}"
],
"subject_types_supported": [
"public"
diff --git a/templates/user/settings/hooks.tmpl b/templates/user/settings/hooks.tmpl
index 477c333220..e2d18001f2 100644
--- a/templates/user/settings/hooks.tmpl
+++ b/templates/user/settings/hooks.tmpl
@@ -1,5 +1,5 @@
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings webhooks")}}
- {{template "repo/settings/webhook/list" .}}
+ {{template "repo/settings/webhook/base_list" .}}
{{template "user/settings/layout_footer" .}}
diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go
index d2228bae79..f8bc33c32a 100644
--- a/tests/integration/oauth_test.go
+++ b/tests/integration/oauth_test.go
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/services/oauth2_provider"
"code.gitea.io/gitea/tests"
@@ -26,24 +27,33 @@ import (
"github.com/stretchr/testify/require"
)
-func TestAuthorizeNoClientID(t *testing.T) {
+func TestOAuth2Provider(t *testing.T) {
defer tests.PrepareTestEnv(t)()
+
+ t.Run("AuthorizeNoClientID", testAuthorizeNoClientID)
+ t.Run("AuthorizeUnregisteredRedirect", testAuthorizeUnregisteredRedirect)
+ t.Run("AuthorizeUnsupportedResponseType", testAuthorizeUnsupportedResponseType)
+ t.Run("AuthorizeUnsupportedCodeChallengeMethod", testAuthorizeUnsupportedCodeChallengeMethod)
+ t.Run("AuthorizeLoginRedirect", testAuthorizeLoginRedirect)
+
+ t.Run("OAuth2WellKnown", testOAuth2WellKnown)
+}
+
+func testAuthorizeNoClientID(t *testing.T) {
req := NewRequest(t, "GET", "/login/oauth/authorize")
ctx := loginUser(t, "user2")
resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
assert.Contains(t, resp.Body.String(), "Client ID not registered")
}
-func TestAuthorizeUnregisteredRedirect(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
+func testAuthorizeUnregisteredRedirect(t *testing.T) {
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=UNREGISTERED&response_type=code&state=thestate")
ctx := loginUser(t, "user1")
resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
assert.Contains(t, resp.Body.String(), "Unregistered Redirect URI")
}
-func TestAuthorizeUnsupportedResponseType(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
+func testAuthorizeUnsupportedResponseType(t *testing.T) {
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate")
ctx := loginUser(t, "user1")
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
@@ -53,8 +63,7 @@ func TestAuthorizeUnsupportedResponseType(t *testing.T) {
assert.Equal(t, "Only code response type is supported.", u.Query().Get("error_description"))
}
-func TestAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
+func testAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=UNEXPECTED")
ctx := loginUser(t, "user1")
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
@@ -64,8 +73,7 @@ func TestAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
assert.Equal(t, "unsupported code challenge method", u.Query().Get("error_description"))
}
-func TestAuthorizeLoginRedirect(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
+func testAuthorizeLoginRedirect(t *testing.T) {
req := NewRequest(t, "GET", "/login/oauth/authorize")
assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login")
}
@@ -903,3 +911,23 @@ func TestOAuth_GrantScopesClaimAllGroups(t *testing.T) {
assert.Contains(t, userinfoParsed.Groups, group)
}
}
+
+func testOAuth2WellKnown(t *testing.T) {
+ urlOpenidConfiguration := "/.well-known/openid-configuration"
+
+ defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
+ req := NewRequest(t, "GET", urlOpenidConfiguration)
+ resp := MakeRequest(t, req, http.StatusOK)
+ var respMap map[string]any
+ DecodeJSON(t, resp, &respMap)
+ assert.Equal(t, "https://try.gitea.io", respMap["issuer"])
+ assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"])
+ assert.Equal(t, "https://try.gitea.io/login/oauth/access_token", respMap["token_endpoint"])
+ assert.Equal(t, "https://try.gitea.io/login/oauth/keys", respMap["jwks_uri"])
+ assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"])
+ assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"])
+ assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"])
+
+ defer test.MockVariableValue(&setting.OAuth2.Enabled, false)()
+ MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound)
+}
diff --git a/web_src/css/base.css b/web_src/css/base.css
index 529ddd5386..2b7a47edf1 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -1031,19 +1031,6 @@ table th[data-sortt-desc] .svg {
min-height: 0;
}
-.precolors {
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin-left: 1em;
-}
-
-.precolors .color {
- display: inline-block;
- width: 15px;
- height: 15px;
-}
-
.ui.dropdown:not(.button) {
line-height: var(--line-height-default); /* the dropdown doesn't have default line-height, use this to make the dropdown icon align with plain dropdown */
}
diff --git a/web_src/css/features/colorpicker.css b/web_src/css/features/colorpicker.css
index b7436783df..4c517e6348 100644
--- a/web_src/css/features/colorpicker.css
+++ b/web_src/css/features/colorpicker.css
@@ -1,15 +1,13 @@
-.js-color-picker-input {
+.color-picker-combo {
display: flex;
- position: relative;
+ position: relative; /* to position the preview square */
}
-.js-color-picker-input input {
- padding-top: 8px !important;
- padding-bottom: 8px !important;
+.color-picker-combo input {
padding-left: 32px !important;
}
-.js-color-picker-input .preview-square {
+.color-picker-combo .preview-square {
position: absolute;
aspect-ratio: 1;
height: 16px;
@@ -22,7 +20,7 @@
background-size: 8px 8px;
}
-.js-color-picker-input .preview-square::after {
+.color-picker-combo .preview-square::after {
content: "";
position: absolute;
width: 100%;
@@ -31,6 +29,26 @@
background-color: currentcolor;
}
+.color-picker-combo .precolors {
+ display: flex;
+ margin-left: 1em;
+ align-items: center;
+ gap: 0.125em;
+}
+
+.color-picker-combo .precolors .generate-random-color {
+ padding: 0;
+ width: 30px;
+ height: 30px;
+ min-height: 0;
+}
+
+.color-picker-combo .precolors .color {
+ display: inline-block;
+ width: 15px;
+ height: 15px;
+}
+
hex-color-picker {
width: 180px;
height: 120px;
diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css
index 7fd5150970..25cb530f85 100644
--- a/web_src/css/features/projects.css
+++ b/web_src/css/features/projects.css
@@ -71,7 +71,7 @@
.card-attachment-images {
display: inline-block;
white-space: nowrap;
- overflow: scroll;
+ overflow: auto;
cursor: default;
scroll-snap-type: x mandatory;
text-align: center;
@@ -85,6 +85,7 @@
scroll-snap-align: center;
margin-right: 2px;
aspect-ratio: 1;
+ object-fit: contain;
}
.card-attachment-images img:only-child {
diff --git a/web_src/js/features/colorpicker.ts b/web_src/js/features/colorpicker.ts
index b99e2f8c45..66d1fcb72a 100644
--- a/web_src/js/features/colorpicker.ts
+++ b/web_src/js/features/colorpicker.ts
@@ -1,18 +1,19 @@
import {createTippy} from '../modules/tippy.ts';
import type {DOMEvent} from '../utils/dom.ts';
+import {registerGlobalInitFunc} from '../modules/observer.ts';
export async function initColorPickers() {
- const els = document.querySelectorAll('.js-color-picker-input');
- if (!els.length) return;
-
- await Promise.all([
- import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'),
- import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'),
- ]);
-
- for (const el of els) {
+ let imported = false;
+ registerGlobalInitFunc('initColorPicker', async (el) => {
+ if (!imported) {
+ await Promise.all([
+ import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'),
+ import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'),
+ ]);
+ imported = true;
+ }
initPicker(el);
- }
+ });
}
function updateSquare(el: HTMLElement, newValue: string): void {
@@ -55,13 +56,20 @@ function initPicker(el: HTMLElement): void {
},
});
- // init precolors
+ // init random color & precolors
+ const setSelectedColor = (color: string) => {
+ input.value = color;
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+ updateSquare(square, color);
+ };
+ el.querySelector('.generate-random-color').addEventListener('click', () => {
+ const newValue = `#${Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0')}`;
+ setSelectedColor(newValue);
+ });
for (const colorEl of el.querySelectorAll('.precolors .color')) {
colorEl.addEventListener('click', (e: DOMEvent) => {
const newValue = e.target.getAttribute('data-color-hex');
- input.value = newValue;
- input.dispatchEvent(new Event('input', {bubbles: true}));
- updateSquare(square, newValue);
+ setSelectedColor(newValue);
});
}
}
diff --git a/web_src/js/features/comp/LabelEdit.ts b/web_src/js/features/comp/LabelEdit.ts
index 423440129c..3e27eac1c5 100644
--- a/web_src/js/features/comp/LabelEdit.ts
+++ b/web_src/js/features/comp/LabelEdit.ts
@@ -24,7 +24,7 @@ export function initCompLabelEdit(pageSelector: string) {
const elIsArchivedField = elModal.querySelector('.label-is-archived-input-field');
const elIsArchivedInput = elModal.querySelector('.label-is-archived-input');
const elDescInput = elModal.querySelector('.label-desc-input');
- const elColorInput = elModal.querySelector('.js-color-picker-input input');
+ const elColorInput = elModal.querySelector('.color-picker-combo input');
const syncModalUi = () => {
const hasScope = nameHasScope(elNameInput.value);