Skip to content

Commit 93e7a01

Browse files
committed
move view-as dropdown to sidebar
1 parent 0202a09 commit 93e7a01

File tree

5 files changed

+79
-29
lines changed

5 files changed

+79
-29
lines changed

modules/templates/helper.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,16 @@ func isQueryParamEmpty(v any) bool {
272272
// It omits the nil, false, zero int/int64 and empty string values,
273273
// because they are default empty values for "ctx.FormXxx" calls.
274274
// If 0 or false need to be included, use string values: "0" and "false".
275+
// Build rules:
276+
// * Even parameters: always build as query string: a=b&c=d
277+
// * Odd parameters:
278+
// * * {"/anything", param-pairs...} => "/?param-paris"
279+
// * * {"anything?old-params", new-param-pairs...} => "anything?old-params&new-param-paris"
280+
// * * Otherwise: {"old&params", new-param-pairs...} => "old&params&new-param-paris"
281+
// * * Other behaviors are undefined yet.
275282
func QueryBuild(a ...any) template.URL {
276283
var reqPath, s string
284+
hasTrailingSep := false
277285
if len(a)%2 == 1 {
278286
if v, ok := a[0].(string); ok {
279287
s = v
@@ -282,9 +290,15 @@ func QueryBuild(a ...any) template.URL {
282290
} else {
283291
panic("QueryBuild: invalid argument")
284292
}
285-
if s1, s2, ok := strings.Cut(s, "?"); ok {
286-
reqPath = s1 + "?"
287-
s = s2
293+
hasTrailingSep = s != "&" && strings.HasSuffix(s, "&")
294+
if strings.HasPrefix(s, "/") || strings.Contains(s, "?") {
295+
if s1, s2, ok := strings.Cut(s, "?"); ok {
296+
reqPath = s1 + "?"
297+
s = s2
298+
} else {
299+
reqPath += s + "?"
300+
s = ""
301+
}
288302
}
289303
}
290304
for i := len(a) % 2; i < len(a); i += 2 {
@@ -327,11 +341,21 @@ func QueryBuild(a ...any) template.URL {
327341
s = s[:pos1] + s[pos2:]
328342
}
329343
}
330-
if s != "" && s != "&" && s[len(s)-1] == '&' {
344+
if s != "" && s[len(s)-1] == '&' && !hasTrailingSep {
331345
s = s[:len(s)-1]
332346
}
333347
if reqPath != "" {
334-
s = reqPath + s
348+
if s == "" {
349+
s = reqPath
350+
if s != "?" {
351+
s = s[:len(s)-1]
352+
}
353+
} else {
354+
if s[0] == '&' {
355+
s = s[1:]
356+
}
357+
s = reqPath + s
358+
}
335359
}
336360
return template.URL(s)
337361
}

modules/templates/helper_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,29 +124,48 @@ func TestQueryBuild(t *testing.T) {
124124
assert.Equal(t, "", string(QueryBuild()))
125125
assert.Equal(t, "", string(QueryBuild("a", nil, "b", false, "c", 0, "d", "")))
126126
assert.Equal(t, "a=1&b=true", string(QueryBuild("a", 1, "b", "true")))
127+
128+
// path with query parameters
129+
assert.Equal(t, "/?k=1", string(QueryBuild("/", "k", 1)))
130+
assert.Equal(t, "/", string(QueryBuild("/?k=a", "k", 0)))
131+
132+
// no path but question mark with query parameters
127133
assert.Equal(t, "?k=1", string(QueryBuild("?", "k", 1)))
128-
assert.Equal(t, "path?a=b&k=1", string(QueryBuild("path?a=b", "k", 1)))
134+
assert.Equal(t, "?", string(QueryBuild("?", "k", 0)))
135+
assert.Equal(t, "path?k=1", string(QueryBuild("path?", "k", 1)))
136+
assert.Equal(t, "path", string(QueryBuild("path?", "k", 0)))
137+
138+
// only query parameters
129139
assert.Equal(t, "&k=1", string(QueryBuild("&", "k", 1)))
130-
assert.Equal(t, "&a=b&k=1", string(QueryBuild("&a=b", "k", 1)))
140+
assert.Equal(t, "", string(QueryBuild("&", "k", 0)))
141+
assert.Equal(t, "", string(QueryBuild("&k=a", "k", 0)))
142+
assert.Equal(t, "", string(QueryBuild("k=a&", "k", 0)))
143+
assert.Equal(t, "a=1&b=2", string(QueryBuild("a=1", "b", 2)))
144+
assert.Equal(t, "&a=1&b=2", string(QueryBuild("&a=1", "b", 2)))
145+
assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2)))
131146
})
147+
132148
t.Run("replace", func(t *testing.T) {
133149
assert.Equal(t, "a=1&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", 1)))
134150
assert.Equal(t, "a=b&c=1&e=f", string(QueryBuild("a=b&c=d&e=f", "c", 1)))
135151
assert.Equal(t, "a=b&c=d&e=1", string(QueryBuild("a=b&c=d&e=f", "e", 1)))
136152
assert.Equal(t, "a=b&c=d&e=f&k=1", string(QueryBuild("a=b&c=d&e=f", "k", 1)))
137153
})
154+
138155
t.Run("replace-&", func(t *testing.T) {
139156
assert.Equal(t, "&a=1&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", 1)))
140157
assert.Equal(t, "&a=b&c=1&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", 1)))
141158
assert.Equal(t, "&a=b&c=d&e=1", string(QueryBuild("&a=b&c=d&e=f", "e", 1)))
142159
assert.Equal(t, "&a=b&c=d&e=f&k=1", string(QueryBuild("&a=b&c=d&e=f", "k", 1)))
143160
})
161+
144162
t.Run("delete", func(t *testing.T) {
145163
assert.Equal(t, "c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", "")))
146164
assert.Equal(t, "a=b&e=f", string(QueryBuild("a=b&c=d&e=f", "c", "")))
147165
assert.Equal(t, "a=b&c=d", string(QueryBuild("a=b&c=d&e=f", "e", "")))
148166
assert.Equal(t, "a=b&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "k", "")))
149167
})
168+
150169
t.Run("delete-&", func(t *testing.T) {
151170
assert.Equal(t, "&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", "")))
152171
assert.Equal(t, "&a=b&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", "")))

options/locale/locale_en-US.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,6 +2864,9 @@ teams.invite.title = You have been invited to join team <strong>%s</strong> in o
28642864
teams.invite.by = Invited by %s
28652865
teams.invite.description = Please click the button below to join the team.
28662866
2867+
view_as_public_hint = You are viewing the README a public user.
2868+
view_as_member_hint = You are viewing the README a member of this organization.
2869+
28672870
[admin]
28682871
maintenance = Maintenance
28692872
dashboard = Dashboard

templates/org/home.tmpl

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,6 @@
55
<div class="ui container">
66
<div class="ui mobile reversed stackable grid">
77
<div class="ui {{if .ShowMemberAndTeamTab}}eleven wide{{end}} column">
8-
{{if or .ProfileReadmeContent}}
9-
{{if and .ShowMemberAndTeamTab .HasPublicProfileReadme .HasPrivateProfileReadme}}
10-
<div class="ui small secondary filter menu">
11-
<div id="org-home-view-as-dropdown" class="item ui small dropdown jump">
12-
<span class="text">
13-
{{svg "octicon-eye" 14}}
14-
View as: {{if not .IsViewerMember}}{{ctx.Locale.Tr "settings.visibility.public"}}{{else}}{{ctx.Locale.Tr "org.members.member"}}{{end}}
15-
</span>
16-
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
17-
<div class="menu">
18-
<a href="{{QueryBuild $.CurrentURL "view_as" "public"}}" class="item {{if not .IsViewerMember}}selected{{end}}">
19-
{{svg "octicon-check" 14 (Iif (not .IsViewerMember) "" "tw-invisible")}} {{ctx.Locale.Tr "settings.visibility.public"}}
20-
</a>
21-
<a href="{{QueryBuild $.CurrentURL "view_as" "member"}}" class="item {{if .IsViewerMember}}selected{{end}}">
22-
{{svg "octicon-check" 14 (Iif .IsViewerMember "" "tw-invisible")}} {{ctx.Locale.Tr "org.members.member"}}
23-
</a>
24-
</div>
25-
</div>
26-
</div>
27-
{{end}}
28-
{{end}}
298
{{if .ProfileReadmeContent}}
309
<div id="readme_profile" class="markup">{{.ProfileReadmeContent}}</div>
3110
{{end}}
@@ -45,6 +24,31 @@
4524
</div>
4625
<div class="divider"></div>
4726
{{end}}
27+
28+
{{if and .ShowMemberAndTeamTab .HasPublicProfileReadme .HasPrivateProfileReadme}}
29+
<div class="tw-my-4">
30+
<div id="org-home-view-as-dropdown" class="ui dropdown jump">
31+
<span class="text">
32+
{{svg "octicon-eye"}}
33+
View as: {{if not .IsViewerMember}}{{ctx.Locale.Tr "settings.visibility.public"}}{{else}}{{ctx.Locale.Tr "org.members.member"}}{{end}}
34+
</span>
35+
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
36+
<div class="menu">
37+
{{/* TODO: does it really need to use CurrentURL with query parameters? Why not construct a new link with clear parameters */}}
38+
<a href="{{QueryBuild $.CurrentURL "view_as" "public"}}" class="item {{if not .IsViewerMember}}selected{{end}}">
39+
{{svg "octicon-check" 14 (Iif (not .IsViewerMember) "" "tw-invisible")}} {{ctx.Locale.Tr "settings.visibility.public"}}
40+
</a>
41+
<a href="{{QueryBuild $.CurrentURL "view_as" "member"}}" class="item {{if .IsViewerMember}}selected{{end}}">
42+
{{svg "octicon-check" 14 (Iif .IsViewerMember "" "tw-invisible")}} {{ctx.Locale.Tr "org.members.member"}}
43+
</a>
44+
</div>
45+
</div>
46+
<div class="tw-my-2">
47+
{{if .IsViewerMember}}{{ctx.Locale.Tr "org.view_as_member_hint"}}{{else}}{{ctx.Locale.Tr "org.view_as_public_hint"}}{{end}}
48+
</div>
49+
</div>
50+
{{end}}
51+
4852
{{if .NumMembers}}
4953
<h4 class="ui top attached header tw-flex">
5054
<strong class="tw-flex-1">{{ctx.Locale.Tr "org.members"}}</strong>

tests/integration/org_profile_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019 The Gitea Authors. All rights reserved.
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
22
// SPDX-License-Identifier: MIT
33

44
package integration

0 commit comments

Comments
 (0)