diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 965b4a24cd..7529cadca4 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -272,8 +272,16 @@ func isQueryParamEmpty(v any) bool { // It omits the nil, false, zero int/int64 and empty string values, // because they are default empty values for "ctx.FormXxx" calls. // If 0 or false need to be included, use string values: "0" and "false". +// Build rules: +// * Even parameters: always build as query string: a=b&c=d +// * Odd parameters: +// * * {"/anything", param-pairs...} => "/?param-paris" +// * * {"anything?old-params", new-param-pairs...} => "anything?old-params&new-param-paris" +// * * Otherwise: {"old¶ms", new-param-pairs...} => "old¶ms&new-param-paris" +// * * Other behaviors are undefined yet. func QueryBuild(a ...any) template.URL { var reqPath, s string + hasTrailingSep := false if len(a)%2 == 1 { if v, ok := a[0].(string); ok { s = v @@ -282,9 +290,15 @@ func QueryBuild(a ...any) template.URL { } else { panic("QueryBuild: invalid argument") } - if s1, s2, ok := strings.Cut(s, "?"); ok { - reqPath = s1 + "?" - s = s2 + hasTrailingSep = s != "&" && strings.HasSuffix(s, "&") + if strings.HasPrefix(s, "/") || strings.Contains(s, "?") { + if s1, s2, ok := strings.Cut(s, "?"); ok { + reqPath = s1 + "?" + s = s2 + } else { + reqPath += s + "?" + s = "" + } } } for i := len(a) % 2; i < len(a); i += 2 { @@ -327,11 +341,21 @@ func QueryBuild(a ...any) template.URL { s = s[:pos1] + s[pos2:] } } - if s != "" && s != "&" && s[len(s)-1] == '&' { + if s != "" && s[len(s)-1] == '&' && !hasTrailingSep { s = s[:len(s)-1] } if reqPath != "" { - s = reqPath + s + if s == "" { + s = reqPath + if s != "?" { + s = s[:len(s)-1] + } + } else { + if s[0] == '&' { + s = s[1:] + } + s = reqPath + s + } } return template.URL(s) } diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go index 0d09a557fa..e35e8a28f8 100644 --- a/modules/templates/helper_test.go +++ b/modules/templates/helper_test.go @@ -124,29 +124,48 @@ func TestQueryBuild(t *testing.T) { assert.Equal(t, "", string(QueryBuild())) assert.Equal(t, "", string(QueryBuild("a", nil, "b", false, "c", 0, "d", ""))) assert.Equal(t, "a=1&b=true", string(QueryBuild("a", 1, "b", "true"))) + + // path with query parameters + assert.Equal(t, "/?k=1", string(QueryBuild("/", "k", 1))) + assert.Equal(t, "/", string(QueryBuild("/?k=a", "k", 0))) + + // no path but question mark with query parameters assert.Equal(t, "?k=1", string(QueryBuild("?", "k", 1))) - assert.Equal(t, "path?a=b&k=1", string(QueryBuild("path?a=b", "k", 1))) + assert.Equal(t, "?", string(QueryBuild("?", "k", 0))) + assert.Equal(t, "path?k=1", string(QueryBuild("path?", "k", 1))) + assert.Equal(t, "path", string(QueryBuild("path?", "k", 0))) + + // only query parameters assert.Equal(t, "&k=1", string(QueryBuild("&", "k", 1))) - assert.Equal(t, "&a=b&k=1", string(QueryBuild("&a=b", "k", 1))) + assert.Equal(t, "", string(QueryBuild("&", "k", 0))) + assert.Equal(t, "", string(QueryBuild("&k=a", "k", 0))) + assert.Equal(t, "", string(QueryBuild("k=a&", "k", 0))) + assert.Equal(t, "a=1&b=2", string(QueryBuild("a=1", "b", 2))) + assert.Equal(t, "&a=1&b=2", string(QueryBuild("&a=1", "b", 2))) + assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2))) }) + t.Run("replace", func(t *testing.T) { assert.Equal(t, "a=1&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", 1))) assert.Equal(t, "a=b&c=1&e=f", string(QueryBuild("a=b&c=d&e=f", "c", 1))) assert.Equal(t, "a=b&c=d&e=1", string(QueryBuild("a=b&c=d&e=f", "e", 1))) assert.Equal(t, "a=b&c=d&e=f&k=1", string(QueryBuild("a=b&c=d&e=f", "k", 1))) }) + t.Run("replace-&", func(t *testing.T) { assert.Equal(t, "&a=1&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", 1))) assert.Equal(t, "&a=b&c=1&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", 1))) assert.Equal(t, "&a=b&c=d&e=1", string(QueryBuild("&a=b&c=d&e=f", "e", 1))) assert.Equal(t, "&a=b&c=d&e=f&k=1", string(QueryBuild("&a=b&c=d&e=f", "k", 1))) }) + t.Run("delete", func(t *testing.T) { assert.Equal(t, "c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", ""))) assert.Equal(t, "a=b&e=f", string(QueryBuild("a=b&c=d&e=f", "c", ""))) assert.Equal(t, "a=b&c=d", string(QueryBuild("a=b&c=d&e=f", "e", ""))) assert.Equal(t, "a=b&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "k", ""))) }) + t.Run("delete-&", func(t *testing.T) { assert.Equal(t, "&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", ""))) assert.Equal(t, "&a=b&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", ""))) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 93881c26ca..70e8959e42 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2864,6 +2864,9 @@ teams.invite.title = You have been invited to join team %s in o teams.invite.by = Invited by %s teams.invite.description = Please click the button below to join the team. +view_as_public_hint = You are viewing the README a public user. +view_as_member_hint = You are viewing the README a member of this organization. + [admin] maintenance = Maintenance dashboard = Dashboard diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index ce9426a7d0..0a2d6f0b97 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -5,27 +5,6 @@