mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-06 08:27:33 +02:00
Add pagination and search box to org teams list (#37245)
- Add pagination and keyword search to the teams list page - 5 teams shown at most in the overview page Fixes: #34482 Fixes: #36602 Fixes: #37084 Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: Animesh Kumar <83393501+kmranimesh@users.noreply.github.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -88,7 +88,7 @@ func SearchTeam(ctx context.Context, opts *SearchTeamOptions) (TeamList, int64,
|
||||
sess = db.SetSessionPagination(sess, opts)
|
||||
|
||||
teams := make([]*Team, 0, opts.PageSize)
|
||||
count, err := sess.Where(cond).OrderBy("lower_name").FindAndCount(&teams)
|
||||
count, err := sess.Where(cond).OrderBy("CASE WHEN name=? THEN '' ELSE lower_name END", OwnerTeamName).FindAndCount(&teams)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
@@ -2826,7 +2826,7 @@
|
||||
"org.teams.manage_team_member_prompt": "Members are managed through teams. Add users to a team to invite them to this organization.",
|
||||
"org.teams.update_settings": "Update Settings",
|
||||
"org.teams.delete_team": "Delete Team",
|
||||
"org.teams.add_team_member": "Add Team Member",
|
||||
"org.teams.add_team_member": "Add team member",
|
||||
"org.teams.invite_team_member": "Invite to %s",
|
||||
"org.teams.invite_team_member.list": "Pending Invitations",
|
||||
"org.teams.delete_team_title": "Delete Team",
|
||||
|
||||
@@ -98,8 +98,10 @@ func home(ctx *context.Context, viewRepositories bool) {
|
||||
ctx.ServerError("FindOrgMembers", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
|
||||
const orgOverviewTeamsLimit = 5
|
||||
ctx.Data["OrgOverviewMembers"] = members
|
||||
ctx.Data["OrgOverviewTeams"] = ctx.Org.Teams[:min(len(ctx.Org.Teams), orgOverviewTeamsLimit)]
|
||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
@@ -54,13 +55,54 @@ func Teams(ctx *context.Context) {
|
||||
ctx.Data["Title"] = org.FullName
|
||||
ctx.Data["PageIsOrgTeams"] = true
|
||||
|
||||
for _, t := range ctx.Org.Teams {
|
||||
keyword := ctx.FormTrim("q")
|
||||
page := max(ctx.FormInt("page"), 1)
|
||||
pagingNum := setting.UI.MembersPagingNum
|
||||
|
||||
searchTeams := func() (teams []*org_model.Team, count int64, err error) {
|
||||
if keyword == "" {
|
||||
// fast path, use existing teams in context if no need to filter from database
|
||||
count = int64(len(ctx.Org.Teams))
|
||||
start := (page - 1) * pagingNum
|
||||
if start > len(ctx.Org.Teams) {
|
||||
return nil, count, nil
|
||||
}
|
||||
end := min(start+pagingNum, len(ctx.Org.Teams))
|
||||
return ctx.Org.Teams[start:end], count, nil
|
||||
}
|
||||
|
||||
shouldSeeAllOrgTeams, err := context.UserShouldSeeAllOrgTeams(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
opts := &org_model.SearchTeamOptions{
|
||||
OrgID: org.ID,
|
||||
UserID: util.Iif(shouldSeeAllOrgTeams, 0, ctx.Doer.ID),
|
||||
Keyword: keyword,
|
||||
IncludeDesc: true,
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: pagingNum},
|
||||
}
|
||||
return org_model.SearchTeam(ctx, opts)
|
||||
}
|
||||
|
||||
teams, count, err := searchTeams()
|
||||
if err != nil {
|
||||
ctx.ServerError("SearchTeam", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, t := range teams {
|
||||
if err := t.LoadMembers(ctx); err != nil {
|
||||
ctx.ServerError("GetMembers", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
|
||||
ctx.Data["OrgListTeams"] = teams
|
||||
ctx.Data["Keyword"] = keyword
|
||||
pager := context.NewPagination(count, setting.UI.MembersPagingNum, page, 5)
|
||||
pager.AddParamFromRequest(ctx.Req)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplTeams)
|
||||
}
|
||||
@@ -213,7 +255,7 @@ func checkIsOrgMemberAndRedirect(ctx *context.Context, defaultRedirect string) {
|
||||
if isOrgMember, err := org_model.IsOrganizationMember(ctx, ctx.Org.Organization.ID, ctx.Doer.ID); err != nil {
|
||||
ctx.ServerError("IsOrganizationMember", err)
|
||||
return
|
||||
} else if !isOrgMember {
|
||||
} else if !isOrgMember && !ctx.Doer.IsAdmin {
|
||||
if ctx.Org.Organization.Visibility.IsPrivate() {
|
||||
defaultRedirect = setting.AppSubURL + "/"
|
||||
} else {
|
||||
|
||||
@@ -174,23 +174,12 @@ func OrgAssignment(orgAssignmentOpts OrgAssignmentOptions) func(ctx *Context) {
|
||||
}
|
||||
|
||||
// Team.
|
||||
shouldSeeAllTeams, err := UserShouldSeeAllOrgTeams(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("UserShouldSeeAllOrgTeams", err)
|
||||
return
|
||||
}
|
||||
if ctx.Org.IsMember {
|
||||
shouldSeeAllTeams := false
|
||||
if ctx.Org.IsOwner {
|
||||
shouldSeeAllTeams = true
|
||||
} else {
|
||||
teams, err := org.GetUserTeams(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserTeams", err)
|
||||
return
|
||||
}
|
||||
for _, team := range teams {
|
||||
if team.IncludesAllRepositories && team.HasAdminAccess() {
|
||||
shouldSeeAllTeams = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if shouldSeeAllTeams {
|
||||
ctx.Org.Teams, err = org.LoadTeams(ctx)
|
||||
if err != nil {
|
||||
@@ -255,3 +244,25 @@ func OrgAssignment(orgAssignmentOpts OrgAssignmentOptions) func(ctx *Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UserShouldSeeAllOrgTeams tells if a user has permission to view all teams in the org.
|
||||
func UserShouldSeeAllOrgTeams(ctx *Context) (bool, error) {
|
||||
if !ctx.Org.IsMember {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if ctx.Org.IsOwner {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
teams, err := ctx.Org.Organization.GetUserTeams(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, team := range teams {
|
||||
if team.IncludesAllRepositories && team.HasAdminAccess() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -60,10 +60,9 @@
|
||||
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/members"><span>{{.NumMembers}}</span> {{svg "octicon-chevron-right"}}</a>
|
||||
</h4>
|
||||
<div class="ui attached segment members">
|
||||
{{$isMember := .IsOrganizationMember}}
|
||||
{{range .Members}}
|
||||
{{if or $isMember (call $.IsPublicMember .ID)}}
|
||||
<a href="{{.HomeLink}}" title="{{.Name}}{{if .FullName}} ({{.FullName}}){{end}}">{{ctx.AvatarUtils.Avatar . 48}}</a>
|
||||
{{range $memberUser := .OrgOverviewMembers}}
|
||||
{{if or $.IsOrganizationMember (call $.IsPublicMember $memberUser.ID)}}
|
||||
{{template "shared/user/avatarlink" dict "user" $memberUser "size" 32 "tooltip" true}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -74,7 +73,7 @@
|
||||
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/teams"><span>{{.Org.NumTeams}}</span> {{svg "octicon-chevron-right"}}</a>
|
||||
</div>
|
||||
<div class="ui attached table segment teams">
|
||||
{{range .Teams}}
|
||||
{{range .OrgOverviewTeams}}
|
||||
<div class="item">
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong class="team-name">{{.Name}}</strong></a>
|
||||
<p class="tw-text-text-light">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<div class="field {{if .Err_Description}}error{{end}}">
|
||||
<label for="description">{{ctx.Locale.Tr "org.team_desc"}}</label>
|
||||
<input id="description" name="description" value="{{.Team.Description}}">
|
||||
<input id="description" name="description" value="{{.Team.Description}}" maxlength="255">
|
||||
<span class="help">{{ctx.Locale.Tr "org.team_desc_helper"}}</span>
|
||||
</div>
|
||||
{{if not (eq .Team.LowerName "owners")}}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
<div class="ui six wide column">
|
||||
<h4 class="ui top attached header">
|
||||
<h4 class="ui top attached header flex-left-right">
|
||||
<strong>{{.Team.Name}}</strong>
|
||||
<div class="ui right">
|
||||
<div class="flex-center-wrap">
|
||||
{{if .Team.IsMember ctx $.SignedUser.ID}}
|
||||
<form>
|
||||
<button class="ui red tiny button delete-button" data-modal-id="leave-team-sidebar"
|
||||
data-url="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
|
||||
data-name="{{.Team.Name}}">{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
</form>
|
||||
<button class="ui red mini compact button show-modal" data-modal="#org-member-leave-team"
|
||||
data-modal-form.action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/leave?uid={{$.SignedUser.ID}}"
|
||||
data-modal-to-leave-team-name="{{$.Team.Name}}"
|
||||
>{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
{{else if .IsOrganizationOwner}}
|
||||
<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/join">
|
||||
<input type="hidden" name="page" value="team">
|
||||
<button type="submit" class="ui primary tiny button" name="uid" value="{{$.SignedUser.ID}}">{{ctx.Locale.Tr "org.teams.join"}}</button>
|
||||
<button type="submit" class="ui primary mini compact button" name="uid" value="{{$.SignedUser.ID}}">{{ctx.Locale.Tr "org.teams.join"}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -85,12 +84,12 @@
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui g-modal-confirm delete modal" id="leave-team-sidebar">
|
||||
<div class="ui mini modal" id="org-member-leave-team">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "org.teams.leave"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "name")}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
<form class="content ui form form-fetch-action" method="post">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "to-leave-team-name")}}</p>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -8,49 +8,59 @@
|
||||
<div class="tw-flex-1">{{ctx.Locale.Tr "org.teams.manage_team_member_prompt"}}</div>
|
||||
<a class="ui primary button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus"}} {{ctx.Locale.Tr "org.create_new_team"}}</a>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
{{end}}
|
||||
|
||||
<form class="ui form ignore-dirty tw-my-4" method="get" action="{{$.Link}}">
|
||||
<div class="ui fluid action input">
|
||||
<input type="search" name="q" value="{{$.Keyword}}" placeholder="{{ctx.Locale.Tr "search.team_kind"}}" maxlength="255" spellcheck="false">
|
||||
<button class="ui button" type="submit">{{svg "octicon-search"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="ui two column stackable grid">
|
||||
{{range .Teams}}
|
||||
<div class="column">
|
||||
<div class="ui top attached header">
|
||||
<a class="tw-text-text" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
|
||||
<div class="ui right">
|
||||
<a class="ui primary tiny button" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}">{{ctx.Locale.Tr "view"}}</a>
|
||||
{{range $team := $.OrgListTeams}}
|
||||
<div class="column team-item-box">
|
||||
<div class="ui top attached header muted-links flex-left-right team-item-header">
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
|
||||
<div class="flex-center-wrap">
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}">{{.NumMembers}} {{ctx.Locale.Tr "org.lower_members"}}</a>
|
||||
·
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories">{{.NumRepos}} {{ctx.Locale.Tr "org.lower_repositories"}}</a>
|
||||
{{if .IsMember ctx $.SignedUser.ID}}
|
||||
<form>
|
||||
<button class="ui red tiny button delete-button" data-modal-id="leave-team"
|
||||
data-url="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
|
||||
data-name="{{.Name}}">{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
</form>
|
||||
{{else if $.IsOrganizationOwner}}
|
||||
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/join">
|
||||
<button type="submit" class="ui primary tiny button" name="uid" value="{{$.SignedUser.ID}}">{{ctx.Locale.Tr "org.teams.join"}}</button>
|
||||
</form>
|
||||
<button class="ui red mini compact button show-modal" data-modal="#org-member-leave-team"
|
||||
data-modal-form.action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/leave?uid={{$.SignedUser.ID}}"
|
||||
data-modal-to-leave-team-name="{{.Name}}"
|
||||
>{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui attached segment members">
|
||||
{{range .Members}}
|
||||
{{template "shared/user/avatarlink" dict "user" .}}
|
||||
{{end}}
|
||||
{{if $team.Description}}
|
||||
<div class="ui attached header team-item-description">
|
||||
{{if $team.Description}}{{$team.Description}}{{end}}
|
||||
</div>
|
||||
<div class="ui bottom attached header">
|
||||
<p class="team-meta"><a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}">{{.NumMembers}} {{ctx.Locale.Tr "org.lower_members"}}</a> · <a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories">{{.NumRepos}} {{ctx.Locale.Tr "org.lower_repositories"}}</a></p>
|
||||
{{end}}
|
||||
<div class="ui attached segment">
|
||||
<div class="flex-center-wrap">
|
||||
{{range .Members}}
|
||||
{{template "shared/user/avatarlink" dict "user" . "size" 32 "tooltip" true}}
|
||||
{{else}}
|
||||
<a class="flex-text-inline tw-h-[32px]" href="{{$.OrgLink}}/teams/{{$team.LowerName | PathEscape}}">{{ctx.Locale.Tr "org.teams.add_team_member"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "base/paginate" .}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui g-modal-confirm delete modal" id="leave-team">
|
||||
<div class="ui mini modal" id="org-member-leave-team">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "org.teams.leave"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "name")}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
<form class="content ui form form-fetch-action" method="post">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "to-leave-team-name")}}</p>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<a class="avatar-with-link" {{if gt .user.ID 0}}href="{{.user.HomeLink}}"{{end}}>{{ctx.AvatarUtils.Avatar .user (or .size 20)}}</a>
|
||||
<a class="avatar-with-link" {{if .tooltip}}data-tooltip-content="{{.user.Name}}{{if .user.FullName}} ({{.user.FullName}}){{end}}"{{end}} {{if gt .user.ID 0}}href="{{.user.HomeLink}}"{{end}}>{{ctx.AvatarUtils.Avatar .user (or .size 20)}}</a>
|
||||
|
||||
@@ -16,9 +16,12 @@ import (
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -31,6 +34,7 @@ func TestOrg(t *testing.T) {
|
||||
t.Run("OrgMembers", testOrgMembers)
|
||||
t.Run("OrgRestrictedUser", testOrgRestrictedUser)
|
||||
t.Run("TeamSearch", testTeamSearch)
|
||||
t.Run("TeamsPage", testTeamsPage)
|
||||
t.Run("OrgSettings", testOrgSettings)
|
||||
}
|
||||
|
||||
@@ -251,6 +255,67 @@ func testTeamSearch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testTeamsPage(t *testing.T) {
|
||||
// org17 has three teams in fixtures: Owners (id 5), test_team (id 8), review_team (id 9).
|
||||
// user15 is in Owners; user20 is in review_team only; user5 is not a member.
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
|
||||
|
||||
listTeams := func(t *testing.T, session *TestSession, query string) []string {
|
||||
req := NewRequestf(t, "GET", "/org/%s/teams%s", org.Name, query)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
sel := htmlDoc.doc.Find(".ui.top.attached.header strong")
|
||||
names := make([]string, 0, sel.Length())
|
||||
sel.Each(func(_ int, s *goquery.Selection) {
|
||||
names = append(names, s.Text())
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
// Owner sees all teams, "Owners" sorted first regardless of alphabetical order
|
||||
ownerSession := loginUser(t, "user15")
|
||||
assert.Equal(t, []string{"Owners", "review_team", "test_team"}, listTeams(t, ownerSession, ""))
|
||||
|
||||
// Keyword filter narrows by name
|
||||
assert.Equal(t, []string{"review_team"}, listTeams(t, ownerSession, "?q=review"))
|
||||
|
||||
// Non-admin org member sees only the teams they belong to
|
||||
memberSession := loginUser(t, "user20")
|
||||
assert.Equal(t, []string{"review_team"}, listTeams(t, memberSession, ""))
|
||||
|
||||
// Edit review_team so user20 gets full list
|
||||
reviewTeam := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 9})
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/org/%s/teams/%s/edit", org.Name, reviewTeam.Name), map[string]string{
|
||||
"team_name": reviewTeam.Name,
|
||||
"description": reviewTeam.Description,
|
||||
"repo_access": "all",
|
||||
"permission": "admin",
|
||||
"unit_1": "1",
|
||||
"unit_2": "1",
|
||||
"unit_3": "1",
|
||||
"unit_4": "1",
|
||||
"unit_5": "1",
|
||||
"unit_6": "1",
|
||||
"unit_7": "1",
|
||||
"unit_8": "1",
|
||||
"unit_9": "1",
|
||||
"unit_10": "1",
|
||||
})
|
||||
ownerSession.MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, []string{"Owners", "review_team", "test_team"}, listTeams(t, memberSession, ""))
|
||||
|
||||
// Non-member is denied
|
||||
nonMemberSession := loginUser(t, "user5")
|
||||
req = NewRequestf(t, "GET", "/org/%s/teams", org.Name)
|
||||
nonMemberSession.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
t.Run("Pagination", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.UI.MembersPagingNum, 2)()
|
||||
assert.Len(t, listTeams(t, ownerSession, "?page=1"), 2)
|
||||
assert.Equal(t, []string{"test_team"}, listTeams(t, ownerSession, "?page=2"))
|
||||
})
|
||||
}
|
||||
|
||||
func testOrgSettings(t *testing.T) {
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
|
||||
@@ -873,6 +873,25 @@ table th[data-sortt-desc] .svg {
|
||||
gap: var(--gap-block);
|
||||
}
|
||||
|
||||
/* TODO: use this to replace all existing "flex + justify-between" (there are quite a lot) */
|
||||
.flex-left-right {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--gap-block);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* TODO: use this to replace all existing "flex + wrap" and (there are quite a lot of) */
|
||||
.flex-center-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--gap-block);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ui.list.flex-items-block > .item,
|
||||
.ui.vertical.menu.flex-items-block > .item,
|
||||
.ui.form .field > label.flex-text-block, /* override fomantic "block" style */
|
||||
|
||||
@@ -18,24 +18,17 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.page-content.organization #org-info .meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
.page-content.organization .team-item-box > .team-item-header {
|
||||
min-height: 50px; /* the header sometimes contains a mini button, sometimes not, so we set a min-height to make sure the layout is consistent */
|
||||
}
|
||||
|
||||
.page-content.organization .ui.top.header .ui.right {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.page-content.organization .teams .item {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.page-content.organization .members .ui.avatar {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
.page-content.organization .team-item-box .team-item-description {
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--color-text-light-3);
|
||||
}
|
||||
|
||||
.organization.invite #invite-box {
|
||||
|
||||
Reference in New Issue
Block a user