Merge pull request 'Allow users to hide all "Add more units..." hints' (#2533) from algernon/forgejo:less-is-more into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2533 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
commit
0bfd4ca532
15 changed files with 233 additions and 24 deletions
|
@ -50,6 +50,8 @@ var migrations = []*Migration{
|
||||||
NewMigration("create the forgejo_repo_flag table", forgejo_v1_22.CreateRepoFlagTable),
|
NewMigration("create the forgejo_repo_flag table", forgejo_v1_22.CreateRepoFlagTable),
|
||||||
// v5 -> v6
|
// v5 -> v6
|
||||||
NewMigration("Add wiki_branch to repository", forgejo_v1_22.AddWikiBranchToRepository),
|
NewMigration("Add wiki_branch to repository", forgejo_v1_22.AddWikiBranchToRepository),
|
||||||
|
// v6 -> v7
|
||||||
|
NewMigration("Add enable_repo_unit_hints to the user table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||||
|
|
17
models/forgejo_migrations/v1_22/v7.go
Normal file
17
models/forgejo_migrations/v1_22/v7.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_22 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddUserRepoUnitHintsSetting(x *xorm.Engine) error {
|
||||||
|
type User struct {
|
||||||
|
ID int64
|
||||||
|
EnableRepoUnitHints bool `xorm:"NOT NULL DEFAULT true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Sync(&User{})
|
||||||
|
}
|
|
@ -146,6 +146,7 @@ type User struct {
|
||||||
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
||||||
Theme string `xorm:"NOT NULL DEFAULT ''"`
|
Theme string `xorm:"NOT NULL DEFAULT ''"`
|
||||||
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
EnableRepoUnitHints bool `xorm:"NOT NULL DEFAULT true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -74,6 +74,7 @@ type UserSettings struct {
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
DiffViewStyle string `json:"diff_view_style"`
|
DiffViewStyle string `json:"diff_view_style"`
|
||||||
|
EnableRepoUnitHints bool `json:"enable_repo_unit_hints"`
|
||||||
// Privacy
|
// Privacy
|
||||||
HideEmail bool `json:"hide_email"`
|
HideEmail bool `json:"hide_email"`
|
||||||
HideActivity bool `json:"hide_activity"`
|
HideActivity bool `json:"hide_activity"`
|
||||||
|
@ -89,6 +90,7 @@ type UserSettingsOptions struct {
|
||||||
Language *string `json:"language"`
|
Language *string `json:"language"`
|
||||||
Theme *string `json:"theme"`
|
Theme *string `json:"theme"`
|
||||||
DiffViewStyle *string `json:"diff_view_style"`
|
DiffViewStyle *string `json:"diff_view_style"`
|
||||||
|
EnableRepoUnitHints *bool `json:"enable_repo_unit_hints"`
|
||||||
// Privacy
|
// Privacy
|
||||||
HideEmail *bool `json:"hide_email"`
|
HideEmail *bool `json:"hide_email"`
|
||||||
HideActivity *bool `json:"hide_activity"`
|
HideActivity *bool `json:"hide_activity"`
|
||||||
|
|
|
@ -711,6 +711,11 @@ continue = Continue
|
||||||
cancel = Cancel
|
cancel = Cancel
|
||||||
language = Language
|
language = Language
|
||||||
ui = Theme
|
ui = Theme
|
||||||
|
hints = Hints
|
||||||
|
additional_repo_units_hint_description = Display an "Add more units..." button for repositories that do not have all available units enabled.
|
||||||
|
additional_repo_units_hint = Encourage enabling additional repository units
|
||||||
|
update_hints = Update hints
|
||||||
|
update_hints_success = Hints have been updated.
|
||||||
hidden_comment_types = Hidden comment types
|
hidden_comment_types = Hidden comment types
|
||||||
hidden_comment_types_description = Comment types checked here will not be shown inside issue pages. Checking "Label" for example removes all "<user> added/removed <label>" comments.
|
hidden_comment_types_description = Comment types checked here will not be shown inside issue pages. Checking "Label" for example removes all "<user> added/removed <label>" comments.
|
||||||
hidden_comment_types.ref_tooltip = Comments where this issue was referenced from another issue/commit/…
|
hidden_comment_types.ref_tooltip = Comments where this issue was referenced from another issue/commit/…
|
||||||
|
|
|
@ -55,6 +55,7 @@ func UpdateUserSettings(ctx *context.APIContext) {
|
||||||
DiffViewStyle: optional.FromPtr(form.DiffViewStyle),
|
DiffViewStyle: optional.FromPtr(form.DiffViewStyle),
|
||||||
KeepEmailPrivate: optional.FromPtr(form.HideEmail),
|
KeepEmailPrivate: optional.FromPtr(form.HideEmail),
|
||||||
KeepActivityPrivate: optional.FromPtr(form.HideActivity),
|
KeepActivityPrivate: optional.FromPtr(form.HideActivity),
|
||||||
|
EnableRepoUnitHints: optional.FromPtr(form.EnableRepoUnitHints),
|
||||||
}
|
}
|
||||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||||
ctx.InternalServerError(err)
|
ctx.InternalServerError(err)
|
||||||
|
|
|
@ -393,6 +393,25 @@ func UpdateUserLang(ctx *context.Context) {
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUserHints updates a user's hints settings
|
||||||
|
func UpdateUserHints(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.UpdateHintsForm)
|
||||||
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
|
ctx.Data["PageIsSettingsAppearance"] = true
|
||||||
|
|
||||||
|
opts := &user_service.UpdateOptions{
|
||||||
|
EnableRepoUnitHints: optional.Some(form.EnableRepoUnitHints),
|
||||||
|
}
|
||||||
|
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||||
|
ctx.ServerError("UpdateUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("User settings updated: %s", ctx.Doer.Name)
|
||||||
|
ctx.Flash.Success(translation.NewLocale(ctx.Doer.Language).TrString("settings.update_hints_success"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateUserHiddenComments update a user's shown comment types
|
// UpdateUserHiddenComments update a user's shown comment types
|
||||||
func UpdateUserHiddenComments(ctx *context.Context) {
|
func UpdateUserHiddenComments(ctx *context.Context) {
|
||||||
err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes, forms.UserHiddenCommentTypesFromRequest(ctx).String())
|
err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes, forms.UserHiddenCommentTypesFromRequest(ctx).String())
|
||||||
|
|
|
@ -568,6 +568,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Group("/appearance", func() {
|
m.Group("/appearance", func() {
|
||||||
m.Get("", user_setting.Appearance)
|
m.Get("", user_setting.Appearance)
|
||||||
m.Post("/language", web.Bind(forms.UpdateLanguageForm{}), user_setting.UpdateUserLang)
|
m.Post("/language", web.Bind(forms.UpdateLanguageForm{}), user_setting.UpdateUserLang)
|
||||||
|
m.Post("/hints", web.Bind(forms.UpdateHintsForm{}), user_setting.UpdateUserHints)
|
||||||
m.Post("/hidden_comments", user_setting.UpdateUserHiddenComments)
|
m.Post("/hidden_comments", user_setting.UpdateUserHiddenComments)
|
||||||
m.Post("/theme", web.Bind(forms.UpdateThemeForm{}), user_setting.UpdateUIThemePost)
|
m.Post("/theme", web.Bind(forms.UpdateThemeForm{}), user_setting.UpdateUIThemePost)
|
||||||
})
|
})
|
||||||
|
|
|
@ -95,6 +95,7 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
|
||||||
HideEmail: user.KeepEmailPrivate,
|
HideEmail: user.KeepEmailPrivate,
|
||||||
HideActivity: user.KeepActivityPrivate,
|
HideActivity: user.KeepActivityPrivate,
|
||||||
DiffViewStyle: user.DiffViewStyle,
|
DiffViewStyle: user.DiffViewStyle,
|
||||||
|
EnableRepoUnitHints: user.EnableRepoUnitHints,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -234,6 +234,11 @@ type UpdateLanguageForm struct {
|
||||||
Language string
|
Language string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateHintsForm form for updating user hint settings
|
||||||
|
type UpdateHintsForm struct {
|
||||||
|
EnableRepoUnitHints bool
|
||||||
|
}
|
||||||
|
|
||||||
// Validate validates the fields
|
// Validate validates the fields
|
||||||
func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||||
ctx := context.GetValidateContext(req)
|
ctx := context.GetValidateContext(req)
|
||||||
|
|
|
@ -37,6 +37,7 @@ type UpdateOptions struct {
|
||||||
EmailNotificationsPreference optional.Option[string]
|
EmailNotificationsPreference optional.Option[string]
|
||||||
SetLastLogin bool
|
SetLastLogin bool
|
||||||
RepoAdminChangeTeamAccess optional.Option[bool]
|
RepoAdminChangeTeamAccess optional.Option[bool]
|
||||||
|
EnableRepoUnitHints optional.Option[bool]
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) error {
|
func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) error {
|
||||||
|
@ -83,6 +84,11 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
|
||||||
|
|
||||||
cols = append(cols, "diff_view_style")
|
cols = append(cols, "diff_view_style")
|
||||||
}
|
}
|
||||||
|
if opts.EnableRepoUnitHints.Has() {
|
||||||
|
u.EnableRepoUnitHints = opts.EnableRepoUnitHints.Value()
|
||||||
|
|
||||||
|
cols = append(cols, "enable_repo_unit_hints")
|
||||||
|
}
|
||||||
|
|
||||||
if opts.AllowGitHook.Has() {
|
if opts.AllowGitHook.Has() {
|
||||||
u.AllowGitHook = opts.AllowGitHook.Value()
|
u.AllowGitHook = opts.AllowGitHook.Value()
|
||||||
|
|
|
@ -172,7 +172,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .Permission.IsAdmin}}
|
{{if .Permission.IsAdmin}}
|
||||||
{{if not (.Repository.AllUnitsEnabled ctx)}}
|
{{if and .SignedUser.EnableRepoUnitHints (not (.Repository.AllUnitsEnabled ctx))}}
|
||||||
<a class="{{if .PageIsRepoSettingsUnits}}active {{end}}item" href="{{.RepoLink}}/settings/units">
|
<a class="{{if .PageIsRepoSettingsUnits}}active {{end}}item" href="{{.RepoLink}}/settings/units">
|
||||||
{{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
|
{{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
|
||||||
</a>
|
</a>
|
||||||
|
|
8
templates/swagger/v1_json.tmpl
generated
8
templates/swagger/v1_json.tmpl
generated
|
@ -23853,6 +23853,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "DiffViewStyle"
|
"x-go-name": "DiffViewStyle"
|
||||||
},
|
},
|
||||||
|
"enable_repo_unit_hints": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "EnableRepoUnitHints"
|
||||||
|
},
|
||||||
"full_name": {
|
"full_name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "FullName"
|
"x-go-name": "FullName"
|
||||||
|
@ -23897,6 +23901,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "DiffViewStyle"
|
"x-go-name": "DiffViewStyle"
|
||||||
},
|
},
|
||||||
|
"enable_repo_unit_hints": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "EnableRepoUnitHints"
|
||||||
|
},
|
||||||
"full_name": {
|
"full_name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "FullName"
|
"x-go-name": "FullName"
|
||||||
|
|
|
@ -66,6 +66,25 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hints -->
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{ctx.Locale.Tr "settings.hints"}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<form class="ui form" action="{{.Link}}/hints" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div class="inline field">
|
||||||
|
<div class="ui checkbox" data-tooltip-content="{{ctx.Locale.Tr "settings.additional_repo_units_hint_description"}}">
|
||||||
|
<input name="enable_repo_unit_hints" type="checkbox" {{if $.SignedUser.EnableRepoUnitHints}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.additional_repo_units_hint"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_hints"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Shown comment event types -->
|
<!-- Shown comment event types -->
|
||||||
<h4 class="ui top attached header">
|
<h4 class="ui top attached header">
|
||||||
{{ctx.Locale.Tr "settings.hidden_comment_types"}}
|
{{ctx.Locale.Tr "settings.hidden_comment_types"}}
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -306,3 +308,123 @@ func TestUserLocationMapLink(t *testing.T) {
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
htmlDoc.AssertElement(t, `a[href="https://example/foo/A%2Fb"]`, true)
|
htmlDoc.AssertElement(t, `a[href="https://example/foo/A%2Fb"]`, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserHints(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
|
||||||
|
// Create a known-good repo, with only one unit enabled
|
||||||
|
repo, _, f := CreateDeclarativeRepo(t, user, "", []unit_model.Type{
|
||||||
|
unit_model.TypeCode,
|
||||||
|
}, []unit_model.Type{
|
||||||
|
unit_model.TypePullRequests,
|
||||||
|
unit_model.TypeProjects,
|
||||||
|
unit_model.TypePackages,
|
||||||
|
unit_model.TypeActions,
|
||||||
|
unit_model.TypeIssues,
|
||||||
|
unit_model.TypeWiki,
|
||||||
|
}, nil)
|
||||||
|
defer f()
|
||||||
|
|
||||||
|
ensureRepoUnitHints := func(t *testing.T, hints bool) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
|
||||||
|
EnableRepoUnitHints: &hints,
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var userSettings api.UserSettings
|
||||||
|
DecodeJSON(t, resp, &userSettings)
|
||||||
|
assert.Equal(t, hints, userSettings.EnableRepoUnitHints)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("API", func(t *testing.T) {
|
||||||
|
t.Run("setting hints on and off", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
ensureRepoUnitHints(t, true)
|
||||||
|
ensureRepoUnitHints(t, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("retrieving settings", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
for _, v := range []bool{true, false} {
|
||||||
|
ensureRepoUnitHints(t, v)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var userSettings api.UserSettings
|
||||||
|
DecodeJSON(t, resp, &userSettings)
|
||||||
|
assert.Equal(t, v, userSettings.EnableRepoUnitHints)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("user settings", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// Set a known-good state, that isn't the default
|
||||||
|
ensureRepoUnitHints(t, false)
|
||||||
|
|
||||||
|
assertHintState := func(t *testing.T, enabled bool) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user/settings/appearance")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
_, hintChecked := htmlDoc.Find(`input[name="enable_repo_unit_hints"]`).Attr("checked")
|
||||||
|
assert.Equal(t, enabled, hintChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("view", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
assertHintState(t, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("change", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "POST", "/user/settings/appearance/hints", map[string]string{
|
||||||
|
"_csrf": GetCSRF(t, session, "/user/settings/appearance"),
|
||||||
|
"enable_repo_unit_hints": "true",
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
|
assertHintState(t, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("repo view", func(t *testing.T) {
|
||||||
|
assertAddMore := func(t *testing.T, present bool) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", repo.Link())
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
htmlDoc.AssertElement(t, fmt.Sprintf("a[href='%s/settings/units']", repo.Link()), present)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("hints enabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
ensureRepoUnitHints(t, true)
|
||||||
|
assertAddMore(t, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("hints disabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
ensureRepoUnitHints(t, false)
|
||||||
|
assertAddMore(t, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue