Skip to content

Commit 7d8a0e8

Browse files
committed
Merge branch 'main' into lunny/improve_get_treepath_latest_commit
2 parents 2f05073 + 7bb7ba1 commit 7d8a0e8

File tree

9 files changed

+98
-33
lines changed

9 files changed

+98
-33
lines changed

custom/conf/app.example.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,9 @@ LEVEL = Info
13391339
;; Number of repos that are displayed on one page
13401340
;REPO_PAGING_NUM = 15
13411341

1342+
;; Number of orgs that are displayed on profile page
1343+
;ORG_PAGING_NUM = 15
1344+
13421345
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
13431346
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
13441347
;[ui.meta]

modules/setting/ui.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ var UI = struct {
6363
} `ini:"ui.admin"`
6464
User struct {
6565
RepoPagingNum int
66+
OrgPagingNum int
6667
} `ini:"ui.user"`
6768
Meta struct {
6869
Author string
@@ -127,8 +128,10 @@ var UI = struct {
127128
},
128129
User: struct {
129130
RepoPagingNum int
131+
OrgPagingNum int
130132
}{
131133
RepoPagingNum: 15,
134+
OrgPagingNum: 15,
132135
},
133136
Meta: struct {
134137
Author string

modules/util/truncate.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package util
55

66
import (
77
"strings"
8+
"unicode"
89
"unicode/utf8"
910
)
1011

@@ -18,6 +19,30 @@ func IsLikelyEllipsisLeftPart(s string) bool {
1819
return strings.HasSuffix(s, utf8Ellipsis) || strings.HasSuffix(s, asciiEllipsis)
1920
}
2021

22+
func ellipsisGuessDisplayWidth(r rune) int {
23+
// To make the truncated string as long as possible,
24+
// CJK/emoji chars are considered as 2-ASCII width but not 3-4 bytes width.
25+
// Here we only make the best guess (better than counting them in bytes),
26+
// it's impossible to 100% correctly determine the width of a rune without a real font and render.
27+
//
28+
// ATTENTION: the guessed width can't be zero, more details in ellipsisDisplayString's comment
29+
if r <= 255 {
30+
return 1
31+
}
32+
33+
switch {
34+
case r == '\u3000': /* ideographic (CJK) characters, still use 2 */
35+
return 2
36+
case unicode.Is(unicode.M, r), /* (Mark) */
37+
unicode.Is(unicode.Cf, r), /* (Other, format) */
38+
unicode.Is(unicode.Cs, r), /* (Other, surrogate) */
39+
unicode.Is(unicode.Z /* (Space) */, r):
40+
return 1
41+
default:
42+
return 2
43+
}
44+
}
45+
2146
// EllipsisDisplayString returns a truncated short string for display purpose.
2247
// The length is the approximate number of ASCII-width in the string (CJK/emoji are 2-ASCII width)
2348
// It appends "…" or "..." at the end of truncated string.
@@ -56,10 +81,7 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
5681
for i, r := range str {
5782
encounterInvalid = encounterInvalid || r == utf8.RuneError
5883
pos = i
59-
runeWidth := 1
60-
if r >= 128 {
61-
runeWidth = 2 // CJK/emoji chars are considered as 2-ASCII width
62-
}
84+
runeWidth := ellipsisGuessDisplayWidth(r)
6385
if used+runeWidth+3 > limit {
6486
break
6587
}
@@ -74,10 +96,7 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
7496
if nextCnt >= 4 {
7597
break
7698
}
77-
nextWidth++
78-
if r >= 128 {
79-
nextWidth++ // CJK/emoji chars are considered as 2-ASCII width
80-
}
99+
nextWidth += ellipsisGuessDisplayWidth(r)
81100
nextCnt++
82101
}
83102
if nextCnt <= 3 && used+nextWidth <= limit {

modules/util/truncate_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,30 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
)
1313

14+
func TestEllipsisGuessDisplayWidth(t *testing.T) {
15+
cases := []struct {
16+
r string
17+
want int
18+
}{
19+
{r: "a", want: 1},
20+
{r: "é", want: 1},
21+
{r: "测", want: 2},
22+
{r: "⚽", want: 2},
23+
{r: "☁️", want: 3}, // 2 runes, it has a mark
24+
{r: "\u200B", want: 1}, // ZWSP
25+
{r: "\u3000", want: 2}, // ideographic space
26+
}
27+
for _, c := range cases {
28+
t.Run(c.r, func(t *testing.T) {
29+
w := 0
30+
for _, r := range c.r {
31+
w += ellipsisGuessDisplayWidth(r)
32+
}
33+
assert.Equal(t, c.want, w, "hex=% x", []byte(c.r))
34+
})
35+
}
36+
}
37+
1438
func TestEllipsisString(t *testing.T) {
1539
cases := []struct {
1640
limit int
@@ -37,6 +61,15 @@ func TestEllipsisString(t *testing.T) {
3761
{limit: 7, input: "测试文本", left: "测试…", right: "…文本"},
3862
{limit: 8, input: "测试文本", left: "测试文本", right: ""},
3963
{limit: 9, input: "测试文本", left: "测试文本", right: ""},
64+
65+
{limit: 6, input: "测试abc", left: "测…", right: "…试abc"},
66+
{limit: 7, input: "测试abc", left: "测试abc", right: ""}, // exactly 7-width
67+
{limit: 8, input: "测试abc", left: "测试abc", right: ""},
68+
69+
{limit: 7, input: "测abc试啊", left: "测ab…", right: "…c试啊"},
70+
{limit: 8, input: "测abc试啊", left: "测abc…", right: "…试啊"},
71+
{limit: 9, input: "测abc试啊", left: "测abc试啊", right: ""}, // exactly 9-width
72+
{limit: 10, input: "测abc试啊", left: "测abc试啊", right: ""},
4073
}
4174
for _, c := range cases {
4275
t.Run(fmt.Sprintf("%s(%d)", c.input, c.limit), func(t *testing.T) {

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ joined_on = Joined on %s
649649
repositories = Repositories
650650
activity = Public Activity
651651
followers = Followers
652+
show_more = Show More
652653
starred = Starred Repositories
653654
watched = Watched Repositories
654655
code = Code

routers/web/shared/user/header.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,20 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
6161
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
6262
UserID: ctx.ContextUser.ID,
6363
IncludePrivate: showPrivate,
64+
ListOptions: db.ListOptions{
65+
Page: 1,
66+
// query one more results (without a separate counting) to see whether we need to add the "show more orgs" link
67+
PageSize: setting.UI.User.OrgPagingNum + 1,
68+
},
6469
})
6570
if err != nil {
6671
ctx.ServerError("FindOrgs", err)
6772
return
6873
}
74+
if len(orgs) > setting.UI.User.OrgPagingNum {
75+
orgs = orgs[:setting.UI.User.OrgPagingNum]
76+
ctx.Data["ShowMoreOrgs"] = true
77+
}
6978
ctx.Data["Orgs"] = orgs
7079
ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(ctx, orgs, ctx.Doer)
7180

routers/web/user/profile.go

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
activities_model "code.gitea.io/gitea/models/activities"
1414
"code.gitea.io/gitea/models/db"
15+
"code.gitea.io/gitea/models/organization"
1516
"code.gitea.io/gitea/models/renderhelper"
1617
repo_model "code.gitea.io/gitea/models/repo"
1718
user_model "code.gitea.io/gitea/models/user"
@@ -256,6 +257,21 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
256257
ctx.Data["ProfileReadme"] = profileContent
257258
}
258259
}
260+
case "organizations":
261+
orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{
262+
UserID: ctx.ContextUser.ID,
263+
IncludePrivate: showPrivate,
264+
ListOptions: db.ListOptions{
265+
Page: page,
266+
PageSize: pagingNum,
267+
},
268+
})
269+
if err != nil {
270+
ctx.ServerError("GetUserOrganizations", err)
271+
return
272+
}
273+
ctx.Data["Cards"] = orgs
274+
total = int(count)
259275
default: // default to "repositories"
260276
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
261277
ListOptions: db.ListOptions{
@@ -294,31 +310,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
294310
}
295311

296312
pager := context.NewPagination(total, pagingNum, page, 5)
297-
pager.SetDefaultParams(ctx)
298-
pager.AddParamString("tab", tab)
299-
if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" {
300-
pager.AddParamString("language", language)
301-
}
302-
if tab == "activity" {
303-
if ctx.Data["Date"] != nil {
304-
pager.AddParamString("date", fmt.Sprint(ctx.Data["Date"]))
305-
}
306-
}
307-
if archived.Has() {
308-
pager.AddParamString("archived", fmt.Sprint(archived.Value()))
309-
}
310-
if fork.Has() {
311-
pager.AddParamString("fork", fmt.Sprint(fork.Value()))
312-
}
313-
if mirror.Has() {
314-
pager.AddParamString("mirror", fmt.Sprint(mirror.Value()))
315-
}
316-
if template.Has() {
317-
pager.AddParamString("template", fmt.Sprint(template.Value()))
318-
}
319-
if private.Has() {
320-
pager.AddParamString("private", fmt.Sprint(private.Value()))
321-
}
313+
pager.AddParamFromRequest(ctx.Req)
322314
ctx.Data["Page"] = pager
323315
}
324316

templates/shared/user/profile_big_avatar.tmpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@
9292
</li>
9393
{{end}}
9494
{{end}}
95+
{{if .ShowMoreOrgs}}
96+
<li><a class="tw-align-center" href="{{.ContextUser.HomeLink}}?tab=organizations" data-tooltip-content="{{ctx.Locale.Tr "user.show_more"}}">{{svg "octicon-kebab-horizontal" 28 "icon tw-p-1"}}</a></li>
97+
{{end}}
9598
</ul>
9699
</li>
97100
{{end}}

templates/user/profile.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
{{template "repo/user_cards" .}}
2828
{{else if eq .TabName "overview"}}
2929
<div id="readme_profile" class="markup">{{.ProfileReadme}}</div>
30+
{{else if eq .TabName "organizations"}}
31+
{{template "repo/user_cards" .}}
3032
{{else}}
3133
{{template "shared/repo_search" .}}
3234
{{template "explore/repo_list" .}}

0 commit comments

Comments
 (0)