mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-19 15:00:52 +02:00
POC: Use gomponents for Explore Users page
This commit is contained in:
parent
b0936f4f41
commit
da75b09c90
25
components/components.go
Normal file
25
components/components.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/svg"
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
)
|
||||||
|
|
||||||
|
func If(condition bool, node g.Node) g.Node {
|
||||||
|
if condition {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SVG(icon string, others ...any) g.Node {
|
||||||
|
return g.Raw(string(svg.RenderHTML(icon)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility to add "active" class if condition is true
|
||||||
|
func classIf(condition bool, class string) string {
|
||||||
|
if condition {
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
66
components/explore_navbar.go
Normal file
66
components/explore_navbar.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExploreNavbarProps struct {
|
||||||
|
PageIsExploreRepositories bool
|
||||||
|
UsersPageIsDisabled bool
|
||||||
|
AppSubUrl string
|
||||||
|
PageIsExploreUsers bool
|
||||||
|
PageIsExploreCode bool
|
||||||
|
IsRepoIndexerEnabled bool
|
||||||
|
CodePageIsDisabled bool
|
||||||
|
PageIsExploreOrganizations bool
|
||||||
|
OrganizationsPageIsDisabled bool
|
||||||
|
Locale translation.Locale
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExploreNavbar(data ExploreNavbarProps) g.Node {
|
||||||
|
tr := func(key string) string {
|
||||||
|
return string(data.Locale.Tr(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
isCodeGlobalDisabled := unit.TypeCode.UnitGlobalDisabled()
|
||||||
|
|
||||||
|
return g.El("overflow-menu",
|
||||||
|
gh.Class("ui secondary pointing tabular top attached borderless menu secondary-nav"),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("overflow-menu-items tw-justify-center"),
|
||||||
|
gh.A(
|
||||||
|
gh.Class(classIf(data.PageIsExploreRepositories, "active ")+"item"),
|
||||||
|
gh.Href(data.AppSubUrl+"/explore/repos"),
|
||||||
|
SVG("octicon-repo"),
|
||||||
|
g.Text(" "+tr("explore.repos")),
|
||||||
|
),
|
||||||
|
If(!data.UsersPageIsDisabled,
|
||||||
|
gh.A(
|
||||||
|
gh.Class(classIf(data.PageIsExploreUsers, "active ")+"item"),
|
||||||
|
gh.Href(data.AppSubUrl+"/explore/users"),
|
||||||
|
SVG("octicon-person"),
|
||||||
|
g.Text(" "+tr("explore.users")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
If(!data.OrganizationsPageIsDisabled,
|
||||||
|
gh.A(
|
||||||
|
gh.Class(classIf(data.PageIsExploreOrganizations, "active ")+"item"),
|
||||||
|
gh.Href(data.AppSubUrl+"/explore/organizations"),
|
||||||
|
SVG("octicon-organization"),
|
||||||
|
g.Text(" "+tr("explore.organizations")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
If(!isCodeGlobalDisabled && data.IsRepoIndexerEnabled && !data.CodePageIsDisabled,
|
||||||
|
gh.A(
|
||||||
|
gh.Class(classIf(data.PageIsExploreCode, "active ")+"item"),
|
||||||
|
gh.Href(data.AppSubUrl+"/explore/code"),
|
||||||
|
SVG("octicon-code"),
|
||||||
|
g.Text(" "+tr("explore.code")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
45
components/explore_search.go
Normal file
45
components/explore_search.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExploreSearchMenu(data ExploreUsersPageProps, pageIsExploreUsers bool) g.Node {
|
||||||
|
// Corresponds to templates/explore/search.tmpl
|
||||||
|
|
||||||
|
tr := func(key string) string {
|
||||||
|
return string(data.Locale.Tr(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Group([]g.Node{
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("ui small secondary filter menu tw-items-center tw-mx-0"),
|
||||||
|
gh.Form(
|
||||||
|
gh.Class("ui form ignore-dirty tw-flex-1"),
|
||||||
|
If(pageIsExploreUsers,
|
||||||
|
SearchCombo(data.Locale, data.Keyword, tr("search.user_kind")),
|
||||||
|
),
|
||||||
|
If(!pageIsExploreUsers,
|
||||||
|
SearchCombo(data.Locale, data.Keyword, tr("search.org_kind")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("ui small dropdown type jump item tw-mr-0"),
|
||||||
|
gh.Span(
|
||||||
|
gh.Class("text"),
|
||||||
|
g.Text(tr("repo.issues.filter_sort")),
|
||||||
|
),
|
||||||
|
SVG("octicon-triangle-down", 14, "dropdown icon"),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("menu"),
|
||||||
|
SortOption(data, "newest", tr("repo.issues.filter_sort.latest")),
|
||||||
|
SortOption(data, "oldest", tr("repo.issues.filter_sort.oldest")),
|
||||||
|
SortOption(data, "alphabetically", tr("repo.issues.label.filter_sort.alphabetically")),
|
||||||
|
SortOption(data, "reversealphabetically", tr("repo.issues.label.filter_sort.reverse_alphabetically")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gh.Div(gh.Class("divider")),
|
||||||
|
})
|
||||||
|
}
|
62
components/explore_users_page.go
Normal file
62
components/explore_users_page.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExploreUsersPageProps struct {
|
||||||
|
Title string
|
||||||
|
Locale translation.Locale
|
||||||
|
Keyword string
|
||||||
|
SortType string
|
||||||
|
Users []*user.User
|
||||||
|
// ContextUser *user.User
|
||||||
|
Context *context.Context
|
||||||
|
IsSigned bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExploreUsersPage(data ExploreUsersPageProps) g.Node {
|
||||||
|
// pageIsExplore := true
|
||||||
|
pageIsExploreUsers := true
|
||||||
|
|
||||||
|
head, err := data.Context.HTMLPartial(200, "base/head")
|
||||||
|
if err != nil {
|
||||||
|
panic("could not render head")
|
||||||
|
}
|
||||||
|
|
||||||
|
footer, err := data.Context.HTMLPartial(200, "base/footer")
|
||||||
|
if err != nil {
|
||||||
|
panic("could not render footer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Group([]g.Node{
|
||||||
|
g.Raw(head),
|
||||||
|
gh.Div(
|
||||||
|
gh.Role("main"),
|
||||||
|
gh.Aria("label", data.Title),
|
||||||
|
gh.Class("page-content explore users"),
|
||||||
|
ExploreNavbar(ExploreNavbarProps{
|
||||||
|
Locale: data.Locale,
|
||||||
|
PageIsExploreUsers: pageIsExploreUsers,
|
||||||
|
}),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("ui container"),
|
||||||
|
ExploreSearchMenu(data, true),
|
||||||
|
UserList(UserListProps{
|
||||||
|
// ContextUser: data.ContextUser,
|
||||||
|
Context: data.Context,
|
||||||
|
Users: data.Users,
|
||||||
|
IsSigned: data.IsSigned,
|
||||||
|
Locale: data.Locale,
|
||||||
|
PageIsAdminUsers: false,
|
||||||
|
}),
|
||||||
|
// Pagination(data),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
g.Raw(footer),
|
||||||
|
})
|
||||||
|
}
|
32
components/search_button.go
Normal file
32
components/search_button.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SearchButton(disabled bool, tooltip string) g.Node {
|
||||||
|
// Corresponds to templates/shared/search/button.tmpl
|
||||||
|
|
||||||
|
class := "ui icon button"
|
||||||
|
if disabled {
|
||||||
|
class += " disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
btn := gh.Button(
|
||||||
|
gh.Type("submit"),
|
||||||
|
gh.Class(class),
|
||||||
|
SVG("octicon-search", 16),
|
||||||
|
)
|
||||||
|
|
||||||
|
if tooltip != "" {
|
||||||
|
btn = gh.Button(
|
||||||
|
gh.Type("submit"),
|
||||||
|
gh.Class(class),
|
||||||
|
g.Attr("data-tooltip-content", tooltip),
|
||||||
|
SVG("octicon-search", 16),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return btn
|
||||||
|
}
|
19
components/search_combo.go
Normal file
19
components/search_combo.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SearchCombo(locale translation.Locale, value, placeholder string) g.Node {
|
||||||
|
// Corresponds to templates/shared/search/combo.tmpl
|
||||||
|
|
||||||
|
disabled := false
|
||||||
|
return gh.Div(
|
||||||
|
gh.Class("ui small fluid action input"),
|
||||||
|
SearchInput(value, placeholder, disabled),
|
||||||
|
// TODO SearchModeDropdown
|
||||||
|
SearchButton(disabled, ""),
|
||||||
|
)
|
||||||
|
}
|
20
components/search_input.go
Normal file
20
components/search_input.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SearchInput(value, placeholder string, disabled bool) g.Node {
|
||||||
|
// Corresponds to templates/shared/search/input.tmpl
|
||||||
|
|
||||||
|
return gh.Input(
|
||||||
|
gh.Type("search"),
|
||||||
|
gh.Name("q"),
|
||||||
|
gh.MaxLength("255"),
|
||||||
|
g.Attr("spellcheck", "false"),
|
||||||
|
gh.Value(value),
|
||||||
|
gh.Placeholder(placeholder),
|
||||||
|
If(disabled, gh.Disabled()),
|
||||||
|
)
|
||||||
|
}
|
21
components/sort_option.go
Normal file
21
components/sort_option.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SortOption(data ExploreUsersPageProps, sortType, label string) g.Node {
|
||||||
|
active := ""
|
||||||
|
if data.SortType == sortType {
|
||||||
|
active = "active "
|
||||||
|
}
|
||||||
|
return gh.A(
|
||||||
|
gh.Class(active+"item"),
|
||||||
|
gh.Href(fmt.Sprintf("?sort=%s&q=%s", sortType, url.QueryEscape(data.Keyword))),
|
||||||
|
g.Text(label),
|
||||||
|
)
|
||||||
|
}
|
92
components/user_list.go
Normal file
92
components/user_list.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
templates "code.gitea.io/gitea/modules/templates"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserListProps struct {
|
||||||
|
Users []*user.User
|
||||||
|
// ContextUser *user.User
|
||||||
|
IsSigned bool
|
||||||
|
PageIsAdminUsers bool
|
||||||
|
Locale translation.Locale
|
||||||
|
Context *context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserList(data UserListProps) g.Node {
|
||||||
|
tr := func(key string, args ...any) string {
|
||||||
|
return string(data.Locale.Tr(key, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Users) == 0 {
|
||||||
|
return gh.Div(
|
||||||
|
gh.Class("flex-list"),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("flex-item"),
|
||||||
|
g.Text(tr("search.no_results")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gh.Div(
|
||||||
|
gh.Class("flex-list"),
|
||||||
|
g.Group(g.Map(data.Users, func(u *user.User) g.Node {
|
||||||
|
utils := templates.NewAvatarUtils(data.Context)
|
||||||
|
|
||||||
|
return gh.Div(
|
||||||
|
gh.Class("flex-item tw-items-center"),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("flex-item-leading"),
|
||||||
|
g.Raw(string(utils.Avatar(u, 48))),
|
||||||
|
),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("flex-item-main"),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("flex-item-title"),
|
||||||
|
UserName(UserNameProps{
|
||||||
|
Locale: data.Locale,
|
||||||
|
User: u,
|
||||||
|
}),
|
||||||
|
If(u.Visibility.IsPrivate(),
|
||||||
|
gh.Span(
|
||||||
|
gh.Class("ui basic tiny label"),
|
||||||
|
g.Text(tr("repo.desc.private")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gh.Div(
|
||||||
|
gh.Class("flex-item-body"),
|
||||||
|
If(u.Location != "",
|
||||||
|
gh.Span(
|
||||||
|
gh.Class("flex-text-inline"),
|
||||||
|
SVG("octicon-location", 16),
|
||||||
|
g.Text(u.Location),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
If(u.Email != "" && (data.PageIsAdminUsers || (setting.UI.ShowUserEmail && data.IsSigned && !u.KeepEmailPrivate)),
|
||||||
|
gh.Span(
|
||||||
|
gh.Class("flex-text-inline"),
|
||||||
|
SVG("octicon-mail", 16),
|
||||||
|
gh.A(
|
||||||
|
gh.Href("mailto:"+u.Email),
|
||||||
|
g.Text(u.Email),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gh.Span(
|
||||||
|
gh.Class("flex-text-inline"),
|
||||||
|
SVG("octicon-calendar", 16),
|
||||||
|
g.Raw(tr("user.joined_on", templates.NewDateUtils().AbsoluteShort(u.CreatedUnix))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
26
components/user_name.go
Normal file
26
components/user_name.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
g "maragu.dev/gomponents"
|
||||||
|
gh "maragu.dev/gomponents/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserNameProps struct {
|
||||||
|
User *user.User
|
||||||
|
Locale translation.Locale
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserName(data UserNameProps) g.Node {
|
||||||
|
return gh.A(
|
||||||
|
gh.Class("text muted"),
|
||||||
|
gh.Href(data.User.HomeLink()),
|
||||||
|
g.Group([]g.Node{
|
||||||
|
g.Text(data.User.Name),
|
||||||
|
If(data.User.FullName != "" && data.User.FullName != data.User.Name,
|
||||||
|
g.Text(" ("+data.User.FullName+")"),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
1
go.mod
1
go.mod
@ -311,6 +311,7 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
maragu.dev/gomponents v1.1.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
||||||
|
2
go.sum
2
go.sum
@ -1004,6 +1004,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
|
maragu.dev/gomponents v1.1.0 h1:iCybZZChHr1eSlvkWp/JP3CrZGzctLudQ/JI3sBcO4U=
|
||||||
|
maragu.dev/gomponents v1.1.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
|
||||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||||
|
@ -57,6 +57,17 @@ func (h *HTMLRender) HTML(w io.Writer, status int, tplName TplName, data any, ct
|
|||||||
return t.Execute(w, data)
|
return t.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HTMLRender) Gomponents(w io.Writer, status int, data []byte) error { //nolint:revive
|
||||||
|
if respWriter, ok := w.(http.ResponseWriter); ok {
|
||||||
|
if respWriter.Header().Get("Content-Type") == "" {
|
||||||
|
respWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
}
|
||||||
|
respWriter.WriteHeader(status)
|
||||||
|
}
|
||||||
|
_, err := w.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HTMLRender) TemplateLookup(name string, ctx context.Context) (TemplateExecutor, error) { //nolint:revive
|
func (h *HTMLRender) TemplateLookup(name string, ctx context.Context) (TemplateExecutor, error) { //nolint:revive
|
||||||
tmpls := h.templates.Load()
|
tmpls := h.templates.Load()
|
||||||
if tmpls == nil {
|
if tmpls == nil {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/components"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
@ -126,6 +127,116 @@ func RenderUserSearch(ctx *context.Context, opts user_model.SearchUserOptions, t
|
|||||||
ctx.HTML(http.StatusOK, tplName)
|
ctx.HTML(http.StatusOK, tplName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderUserSearch2(ctx *context.Context, opts user_model.SearchUserOptions, title string) {
|
||||||
|
// Sitemap index for sitemap paths
|
||||||
|
isSitemap := ctx.PathParam("idx") != ""
|
||||||
|
if isSitemap {
|
||||||
|
opts.Page = int(ctx.PathParamInt64("idx"))
|
||||||
|
opts.PageSize = setting.UI.SitemapPagingNum
|
||||||
|
} else {
|
||||||
|
opts.Page = ctx.FormInt("page")
|
||||||
|
}
|
||||||
|
if opts.Page <= 1 {
|
||||||
|
opts.Page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
users []*user_model.User
|
||||||
|
count int64
|
||||||
|
err error
|
||||||
|
orderBy db.SearchOrderBy
|
||||||
|
)
|
||||||
|
|
||||||
|
// we can not set orderBy to `models.SearchOrderByXxx`, because there may be a JOIN in the statement, different tables may have the same name columns
|
||||||
|
|
||||||
|
sortOrder := ctx.FormString("sort")
|
||||||
|
if sortOrder == "" {
|
||||||
|
sortOrder = setting.UI.ExploreDefaultSort
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sortOrder {
|
||||||
|
case "newest":
|
||||||
|
orderBy = "`user`.id DESC"
|
||||||
|
case "oldest":
|
||||||
|
orderBy = "`user`.id ASC"
|
||||||
|
case "leastupdate":
|
||||||
|
orderBy = "`user`.updated_unix ASC"
|
||||||
|
case "reversealphabetically":
|
||||||
|
orderBy = "`user`.name DESC"
|
||||||
|
case "lastlogin":
|
||||||
|
orderBy = "`user`.last_login_unix ASC"
|
||||||
|
case "reverselastlogin":
|
||||||
|
orderBy = "`user`.last_login_unix DESC"
|
||||||
|
case "alphabetically":
|
||||||
|
orderBy = "`user`.name ASC"
|
||||||
|
case "recentupdate":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
// in case the sortType is not valid, we set it to recentupdate
|
||||||
|
sortOrder = "recentupdate"
|
||||||
|
orderBy = "`user`.updated_unix DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["SortType"] = sortOrder
|
||||||
|
|
||||||
|
if opts.SupportedSortOrders != nil && !opts.SupportedSortOrders.Contains(sortOrder) {
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Keyword = ctx.FormTrim("q")
|
||||||
|
opts.OrderBy = orderBy
|
||||||
|
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
|
||||||
|
users, count, err = user_model.SearchUsers(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchUsers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isSitemap {
|
||||||
|
m := sitemap.NewSitemap()
|
||||||
|
for _, item := range users {
|
||||||
|
m.Add(sitemap.URL{URL: item.HTMLURL(), LastMod: item.UpdatedUnix.AsTimePtr()})
|
||||||
|
}
|
||||||
|
ctx.Resp.Header().Set("Content-Type", "text/xml")
|
||||||
|
if _, err := m.WriteTo(ctx.Resp); err != nil {
|
||||||
|
log.Error("Failed writing sitemap: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["Keyword"] = opts.Keyword
|
||||||
|
ctx.Data["Total"] = count
|
||||||
|
ctx.Data["Users"] = users
|
||||||
|
ctx.Data["UsersTwoFaStatus"] = user_model.UserList(users).GetTwoFaStatus(ctx)
|
||||||
|
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
|
||||||
|
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||||
|
|
||||||
|
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
|
||||||
|
pager.AddParamFromRequest(ctx.Req)
|
||||||
|
ctx.Data["Page"] = pager
|
||||||
|
|
||||||
|
var IsSigned bool
|
||||||
|
if val, ok := ctx.Data["IsSigned"]; ok && val != nil {
|
||||||
|
IsSigned = *val.(*bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := components.ExploreUsersPage(components.ExploreUsersPageProps{
|
||||||
|
Title: title,
|
||||||
|
Locale: ctx.Base.Locale,
|
||||||
|
Keyword: opts.Keyword,
|
||||||
|
SortType: sortOrder,
|
||||||
|
Users: users,
|
||||||
|
IsSigned: IsSigned,
|
||||||
|
Context: ctx,
|
||||||
|
})
|
||||||
|
|
||||||
|
var bodyBuffer bytes.Buffer
|
||||||
|
data.Render(&bodyBuffer)
|
||||||
|
|
||||||
|
ctx.Gomponents(http.StatusOK, bodyBuffer.String(), "ExploreUsersPage")
|
||||||
|
}
|
||||||
|
|
||||||
// Users render explore users page
|
// Users render explore users page
|
||||||
func Users(ctx *context.Context) {
|
func Users(ctx *context.Context) {
|
||||||
if setting.Service.Explore.DisableUsersPage {
|
if setting.Service.Explore.DisableUsersPage {
|
||||||
@ -135,8 +246,6 @@ func Users(ctx *context.Context) {
|
|||||||
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
|
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
|
||||||
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
|
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
|
||||||
ctx.Data["Title"] = ctx.Tr("explore")
|
ctx.Data["Title"] = ctx.Tr("explore")
|
||||||
ctx.Data["PageIsExplore"] = true
|
|
||||||
ctx.Data["PageIsExploreUsers"] = true
|
|
||||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||||
|
|
||||||
supportedSortOrders := container.SetOf(
|
supportedSortOrders := container.SetOf(
|
||||||
@ -151,7 +260,7 @@ func Users(ctx *context.Context) {
|
|||||||
ctx.SetFormString("sort", sortOrder)
|
ctx.SetFormString("sort", sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderUserSearch(ctx, user_model.SearchUserOptions{
|
RenderUserSearch2(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeIndividual,
|
Type: user_model.UserTypeIndividual,
|
||||||
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
||||||
@ -159,5 +268,5 @@ func Users(ctx *context.Context) {
|
|||||||
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
|
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
|
||||||
|
|
||||||
SupportedSortOrders: supportedSortOrders,
|
SupportedSortOrders: supportedSortOrders,
|
||||||
}, tplExploreUsers)
|
}, string(ctx.Tr("explore")))
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
type Render interface {
|
type Render interface {
|
||||||
TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
|
TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
|
||||||
HTML(w io.Writer, status int, name templates.TplName, data any, templateCtx context.Context) error
|
HTML(w io.Writer, status int, name templates.TplName, data any, templateCtx context.Context) error
|
||||||
|
Gomponents(w io.Writer, status int, data []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context represents context of a web request.
|
// Context represents context of a web request.
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -91,6 +92,31 @@ func (ctx *Context) HTML(status int, name templates.TplName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) HTMLPartial(status int, name templates.TplName) (string, error) {
|
||||||
|
log.Debug("Partial Template: %s", name)
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
err := ctx.Render.HTML(rec, status, name, ctx.Data, ctx.TemplateContext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rec.Body.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Gomponents(status int, html string, tplName string) {
|
||||||
|
log.Debug("Component: %s", tplName)
|
||||||
|
|
||||||
|
err := ctx.Render.Gomponents(ctx.Resp, status, []byte(html))
|
||||||
|
if err == nil || errors.Is(err, syscall.EPIPE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fmt.Errorf("failed to render component: %s, error: %s", tplName, err)
|
||||||
|
ctx.ServerError("Render failed", err) // show the 500 error page
|
||||||
|
}
|
||||||
|
|
||||||
// JSONTemplate renders the template as JSON response
|
// 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, eg: by JSEscape
|
||||||
func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
|
func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
|
||||||
|
@ -198,3 +198,10 @@ func (tr *MockRender) HTML(w io.Writer, status int, _ templates.TplName, _ any,
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tr *MockRender) Gomponents(w io.Writer, status int, _ string) error {
|
||||||
|
if resp, ok := w.(http.ResponseWriter); ok {
|
||||||
|
resp.WriteHeader(status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user