From 44349760e924400c91ec0c61d7478a4b55a9433b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 26 Mar 2026 11:25:26 -0700 Subject: [PATCH] Support organization setting to set default repositories list sort type --- models/user/setting_options.go | 2 ++ options/locale/locale_en-US.json | 4 +++ routers/web/org/home.go | 11 +++++++- routers/web/org/repo_default_sort.go | 24 ++++++++++++++++ routers/web/org/setting.go | 37 +++++++++++++++++++++++++ services/forms/org.go | 1 + templates/org/settings/options.tmpl | 11 +++++++- templates/shared/repo/search.tmpl | 15 +--------- templates/shared/repo/sort_options.tmpl | 24 ++++++++++++++++ tests/integration/org_test.go | 21 ++++++++++++-- 10 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 routers/web/org/repo_default_sort.go create mode 100644 templates/shared/repo/sort_options.tmpl diff --git a/models/user/setting_options.go b/models/user/setting_options.go index 5867b908d1..7ae7f53191 100644 --- a/models/user/setting_options.go +++ b/models/user/setting_options.go @@ -24,4 +24,6 @@ const ( SettingEmailNotificationGiteaActionsDisabled = "disabled" SettingsKeyActionsConfig = "actions.config" + + SettingsKeyOrgRepoDefaultSort = "org.repo_default_sort" ) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 2ffa130751..8dcd6c1b83 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2739,6 +2739,10 @@ "org.settings.email": "Contact Email Address", "org.settings.website": "Website", "org.settings.location": "Location", + "org.settings.repo_default_sort": "Default repository sort", + "org.settings.repo_default_sort_default": "Instance default", + "org.settings.repo_default_sort_desc": "Used when visitors do not select a sort option on the organization repositories page.", + "org.settings.repo_default_sort_invalid": "Repository sort option is invalid.", "org.settings.permission": "Permissions", "org.settings.repoadminchangeteam": "Repository admin can add and remove access for teams", "org.settings.visibility": "Visibility", diff --git a/routers/web/org/home.go b/routers/web/org/home.go index e18a8de40f..c828dff298 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -55,7 +55,16 @@ func home(ctx *context.Context, viewRepositories bool) { var orderBy db.SearchOrderBy sortOrder := ctx.FormString("sort") if _, ok := repo_model.OrderByFlatMap[sortOrder]; !ok { - sortOrder = setting.UI.ExploreDefaultSort // TODO: add new default sort order for org home? + repoDefaultSort, err := getOrgRepoDefaultSort(ctx, org) + if err != nil { + ctx.ServerError("GetUserSetting", err) + return + } + if repoDefaultSort != "" { + sortOrder = repoDefaultSort + } else { + sortOrder = setting.UI.ExploreDefaultSort + } } ctx.Data["SortType"] = sortOrder orderBy = repo_model.OrderByFlatMap[sortOrder] diff --git a/routers/web/org/repo_default_sort.go b/routers/web/org/repo_default_sort.go new file mode 100644 index 0000000000..0d1cde975a --- /dev/null +++ b/routers/web/org/repo_default_sort.go @@ -0,0 +1,24 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package org + +import ( + "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/services/context" +) + +func getOrgRepoDefaultSort(ctx *context.Context, org *organization.Organization) (string, error) { + defaultSort, err := user_model.GetUserSetting(ctx, org.ID, user_model.SettingsKeyOrgRepoDefaultSort) + if err != nil { + return "", err + } + if defaultSort != "" { + if _, ok := repo_model.OrderByFlatMap[defaultSort]; !ok { + return "", nil + } + } + return defaultSort, nil +} diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index ca1da6617e..00754bbf50 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -48,6 +48,13 @@ func Settings(ctx *context.Context) { ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess ctx.Data["ContextUser"] = ctx.ContextUser + repoDefaultSort, err := getOrgRepoDefaultSort(ctx, ctx.Org.Organization) + if err != nil { + ctx.ServerError("GetUserSetting", err) + return + } + ctx.Data["RepoDefaultSort"] = repoDefaultSort + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { ctx.ServerError("RenderUserOrgHeader", err) return @@ -65,6 +72,9 @@ func SettingsPost(ctx *context.Context) { ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility if ctx.HasError() { + if repoDefaultSort, err := getOrgRepoDefaultSort(ctx, ctx.Org.Organization); err == nil { + ctx.Data["RepoDefaultSort"] = repoDefaultSort + } ctx.HTML(http.StatusOK, tplSettingsOptions) return } @@ -80,6 +90,19 @@ func SettingsPost(ctx *context.Context) { return } + repoDefaultSort := "" + if form.RepoDefaultSort != nil { + repoDefaultSort = *form.RepoDefaultSort + if repoDefaultSort != "" { + if _, ok := repo_model.OrderByFlatMap[repoDefaultSort]; !ok { + ctx.Data["Err_RepoDefaultSort"] = true + ctx.Data["RepoDefaultSort"] = repoDefaultSort + ctx.RenderWithErrDeprecated(ctx.Tr("org.settings.repo_default_sort_invalid"), tplSettingsOptions, &form) + return + } + } + } + opts := &user_service.UpdateOptions{ FullName: optional.FromPtr(form.FullName), Description: optional.FromPtr(form.Description), @@ -96,6 +119,20 @@ func SettingsPost(ctx *context.Context) { return } + if form.RepoDefaultSort != nil { + if repoDefaultSort == "" { + if err := user_model.DeleteUserSetting(ctx, org.ID, user_model.SettingsKeyOrgRepoDefaultSort); err != nil { + ctx.ServerError("DeleteUserSetting", err) + return + } + } else { + if err := user_model.SetUserSetting(ctx, org.ID, user_model.SettingsKeyOrgRepoDefaultSort, repoDefaultSort); err != nil { + ctx.ServerError("SetUserSetting", err) + return + } + } + } + log.Trace("Organization setting updated: %s", org.Name) ctx.Flash.Success(ctx.Tr("org.settings.update_setting_success")) ctx.Redirect(ctx.Org.OrgLink + "/settings") diff --git a/services/forms/org.go b/services/forms/org.go index 8a8106e8cf..b2395cd1fd 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -43,6 +43,7 @@ type UpdateOrgSettingForm struct { Location *string `binding:"MaxSize(50)"` MaxRepoCreation *int RepoAdminChangeTeamAccess *bool + RepoDefaultSort *string `binding:"MaxSize(50)"` } // Validate validates the fields diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 2c17e13937..dee786b328 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -38,9 +38,18 @@ - {{if .SignedUser.IsAdmin}}
+
+ + +

{{ctx.Locale.Tr "org.settings.repo_default_sort_desc"}}

+
+ + {{if .SignedUser.IsAdmin}}
diff --git a/templates/shared/repo/search.tmpl b/templates/shared/repo/search.tmpl index a852e65582..a8ed8a31f4 100644 --- a/templates/shared/repo/search.tmpl +++ b/templates/shared/repo/search.tmpl @@ -36,20 +36,7 @@ {{ctx.Locale.Tr "repo.issues.filter_sort"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
diff --git a/templates/shared/repo/sort_options.tmpl b/templates/shared/repo/sort_options.tmpl new file mode 100644 index 0000000000..fe758b89dd --- /dev/null +++ b/templates/shared/repo/sort_options.tmpl @@ -0,0 +1,24 @@ +{{define "shared/repo/sort_option"}} + {{if eq .Kind "select"}} + + {{else}} + + {{end}} +{{end}} + +{{define "shared/repo/sort_options"}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "newest" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.latest")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "oldest" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.oldest")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "alphabetically" "Label" (ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "reversealphabetically" "Label" (ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "recentupdate" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.recentupdate")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "leastupdate" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.leastupdate")}} + {{if not .DisableStars}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "moststars" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.moststars")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "feweststars" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.feweststars")}} + {{end}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "mostforks" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.mostforks")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "fewestforks" "Label" (ctx.Locale.Tr "repo.issues.filter_sort.fewestforks")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "size" "Label" (ctx.Locale.Tr "repo.issues.label.filter_sort.by_size")}} + {{template "shared/repo/sort_option" dict "CurrentSort" .CurrentSort "Kind" .Kind "Value" "reversesize" "Label" (ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_by_size")}} +{{end}} diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index cedc0406ca..c968f57931 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -255,13 +255,27 @@ func testOrgSettings(t *testing.T) { session := loginUser(t, "user2") req := NewRequestWithValues(t, "POST", "/org/org3/settings", map[string]string{ - "full_name": "org3 new full name", - "email": "org3-new-email@example.com", + "full_name": "org3 new full name", + "email": "org3-new-email@example.com", + "repo_default_sort": "reversealphabetically", }) session.MakeRequest(t, req, http.StatusSeeOther) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) assert.Equal(t, "org3 new full name", org.FullName) assert.Equal(t, "org3-new-email@example.com", org.Email) + repoDefaultSort, err := user_model.GetUserSetting(t.Context(), org.ID, user_model.SettingsKeyOrgRepoDefaultSort) + require.NoError(t, err) + assert.Equal(t, "reversealphabetically", repoDefaultSort) + + req = NewRequest(t, "GET", "/org3") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + sel := htmlDoc.doc.Find("a.name") + expectedRepos := []string{"repo5", "repo3", "repo21"} + assert.Len(t, expectedRepos, len(sel.Nodes)) + for i := range expectedRepos { + assert.Equal(t, expectedRepos[i], strings.TrimSpace(sel.Eq(i).Text())) + } req = NewRequestWithValues(t, "POST", "/org/org3/settings", map[string]string{ "email": "", // empty email means "clear email" @@ -270,4 +284,7 @@ func testOrgSettings(t *testing.T) { org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) assert.Equal(t, "org3 new full name", org.FullName) assert.Empty(t, org.Email) + repoDefaultSort, err = user_model.GetUserSetting(t.Context(), org.ID, user_model.SettingsKeyOrgRepoDefaultSort) + require.NoError(t, err) + assert.Equal(t, "reversealphabetically", repoDefaultSort) }