mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into fork-on-edit
This commit is contained in:
commit
2ba118ac69
|
|
@ -831,6 +831,20 @@ type CountUserFilter struct {
|
||||||
IsActive optional.Option[bool]
|
IsActive optional.Option[bool]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasUsers checks whether there are any users in the database, or only one user exists.
|
||||||
|
func HasUsers(ctx context.Context) (ret struct {
|
||||||
|
HasAnyUser, HasOnlyOneUser bool
|
||||||
|
}, err error,
|
||||||
|
) {
|
||||||
|
res, err := db.GetEngine(ctx).Table(&User{}).Cols("id").Limit(2).Query()
|
||||||
|
if err != nil {
|
||||||
|
return ret, fmt.Errorf("error checking user existence: %w", err)
|
||||||
|
}
|
||||||
|
ret.HasAnyUser = len(res) != 0
|
||||||
|
ret.HasOnlyOneUser = len(res) == 1
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CountUsers returns number of users.
|
// CountUsers returns number of users.
|
||||||
func CountUsers(ctx context.Context, opts *CountUserFilter) int64 {
|
func CountUsers(ctx context.Context, opts *CountUserFilter) int64 {
|
||||||
return countUsers(ctx, opts)
|
return countUsers(ctx, opts)
|
||||||
|
|
|
||||||
|
|
@ -421,6 +421,7 @@ remember_me.compromised = The login token is not valid anymore which may indicat
|
||||||
forgot_password_title= Forgot Password
|
forgot_password_title= Forgot Password
|
||||||
forgot_password = Forgot password?
|
forgot_password = Forgot password?
|
||||||
need_account = Need an account?
|
need_account = Need an account?
|
||||||
|
sign_up_tip = You are registering the first account in the system, which has administrator privileges. Please carefully remember your username and password. If you forget the username or password, please refer to the Gitea documentation to recover the account.
|
||||||
sign_up_now = Register now.
|
sign_up_now = Register now.
|
||||||
sign_up_successful = Account was successfully created. Welcome!
|
sign_up_successful = Account was successfully created. Welcome!
|
||||||
confirmation_mail_sent_prompt_ex = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.
|
confirmation_mail_sent_prompt_ex = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.
|
||||||
|
|
@ -2817,6 +2818,7 @@ team_permission_desc = Permission
|
||||||
team_unit_desc = Allow Access to Repository Sections
|
team_unit_desc = Allow Access to Repository Sections
|
||||||
team_unit_disabled = (Disabled)
|
team_unit_disabled = (Disabled)
|
||||||
|
|
||||||
|
form.name_been_taken = The organisation name "%s" has already been taken.
|
||||||
form.name_reserved = The organization name "%s" is reserved.
|
form.name_reserved = The organization name "%s" is reserved.
|
||||||
form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name.
|
form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name.
|
||||||
form.create_org_not_allowed = You are not allowed to create an organization.
|
form.create_org_not_allowed = You are not allowed to create an organization.
|
||||||
|
|
@ -2838,15 +2840,28 @@ settings.visibility.private_shortname = Private
|
||||||
|
|
||||||
settings.update_settings = Update Settings
|
settings.update_settings = Update Settings
|
||||||
settings.update_setting_success = Organization settings have been updated.
|
settings.update_setting_success = Organization settings have been updated.
|
||||||
settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name.
|
|
||||||
settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed.
|
settings.rename = Rename Organization
|
||||||
|
settings.rename_desc = Changing the organization name will also change your organization's URL and free the old name.
|
||||||
|
settings.rename_success = Organization %[1]s have been renamed to %[2]s successfully.
|
||||||
|
settings.rename_no_change = Organization name is no change.
|
||||||
|
settings.rename_new_org_name = New Organization Name
|
||||||
|
settings.rename_failed = Rename Organization failed because of internal error
|
||||||
|
settings.rename_notices_1 = This operation <strong>CANNOT</strong> be undone.
|
||||||
|
settings.rename_notices_2 = The old name will redirect until it is claimed.
|
||||||
|
|
||||||
settings.update_avatar_success = The organization's avatar has been updated.
|
settings.update_avatar_success = The organization's avatar has been updated.
|
||||||
settings.delete = Delete Organization
|
settings.delete = Delete Organization
|
||||||
settings.delete_account = Delete This Organization
|
settings.delete_account = Delete This Organization
|
||||||
settings.delete_prompt = The organization will be permanently removed. This <strong>CANNOT</strong> be undone!
|
settings.delete_prompt = The organization will be permanently removed. This <strong>CANNOT</strong> be undone!
|
||||||
|
settings.name_confirm = Enter the organization name as confirmation:
|
||||||
|
settings.delete_notices_1 = This operation <strong>CANNOT</strong> be undone.
|
||||||
|
settings.delete_notices_2 = This operation will permanently delete all the <strong>repositories</strong> of <strong>%s</strong> including code, issues, comments, wiki data and collaborator settings.
|
||||||
|
settings.delete_notices_3 = This operation will permanently delete all the <strong>packages</strong> of <strong>%s</strong>.
|
||||||
|
settings.delete_notices_4 = This operation will permanently delete all the <strong>projects</strong> of <strong>%s</strong>.
|
||||||
settings.confirm_delete_account = Confirm Deletion
|
settings.confirm_delete_account = Confirm Deletion
|
||||||
settings.delete_org_title = Delete Organization
|
settings.delete_failed = Delete Organization failed because of internal error
|
||||||
settings.delete_org_desc = This organization will be deleted permanently. Continue?
|
settings.delete_successful = Organization <b>%s</b> has been deleted successfully.
|
||||||
settings.hooks_desc = Add webhooks which will be triggered for <strong>all repositories</strong> under this organization.
|
settings.hooks_desc = Add webhooks which will be triggered for <strong>all repositories</strong> under this organization.
|
||||||
|
|
||||||
settings.labels_desc = Add labels which can be used on issues for <strong>all repositories</strong> under this organization.
|
settings.labels_desc = Add labels which can be used on issues for <strong>all repositories</strong> under this organization.
|
||||||
|
|
|
||||||
|
|
@ -601,5 +601,7 @@ func SubmitInstall(ctx *context.Context) {
|
||||||
// InstallDone shows the "post-install" page, makes it easier to develop the page.
|
// InstallDone shows the "post-install" page, makes it easier to develop the page.
|
||||||
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
|
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
|
||||||
func InstallDone(ctx *context.Context) { //nolint
|
func InstallDone(ctx *context.Context) { //nolint
|
||||||
|
hasUsers, _ := user_model.HasUsers(ctx)
|
||||||
|
ctx.Data["IsAccountCreated"] = hasUsers.HasAnyUser
|
||||||
ctx.HTML(http.StatusOK, tplPostInstall)
|
ctx.HTML(http.StatusOK, tplPostInstall)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -421,9 +421,11 @@ func SignOut(ctx *context.Context) {
|
||||||
// SignUp render the register page
|
// SignUp render the register page
|
||||||
func SignUp(ctx *context.Context) {
|
func SignUp(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("sign_up")
|
ctx.Data["Title"] = ctx.Tr("sign_up")
|
||||||
|
|
||||||
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
|
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
|
||||||
|
|
||||||
|
hasUsers, _ := user_model.HasUsers(ctx)
|
||||||
|
ctx.Data["IsFirstTimeRegistration"] = !hasUsers.HasAnyUser
|
||||||
|
|
||||||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("UserSignUp", err)
|
ctx.ServerError("UserSignUp", err)
|
||||||
|
|
@ -610,7 +612,13 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any,
|
||||||
// sends a confirmation email if required.
|
// sends a confirmation email if required.
|
||||||
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
|
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
|
||||||
// Auto-set admin for the only user.
|
// Auto-set admin for the only user.
|
||||||
if user_model.CountUsers(ctx, nil) == 1 {
|
hasUsers, err := user_model.HasUsers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("HasUsers", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if hasUsers.HasOnlyOneUser {
|
||||||
|
// the only user is the one just created, will set it as admin
|
||||||
opts := &user_service.UpdateOptions{
|
opts := &user_service.UpdateOptions{
|
||||||
IsActive: optional.Some(true),
|
IsActive: optional.Some(true),
|
||||||
IsAdmin: user_service.UpdateOptionFieldFromValue(true),
|
IsAdmin: user_service.UpdateOptionFieldFromValue(true),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||||
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
||||||
|
|
@ -31,8 +32,6 @@ import (
|
||||||
const (
|
const (
|
||||||
// tplSettingsOptions template path for render settings
|
// tplSettingsOptions template path for render settings
|
||||||
tplSettingsOptions templates.TplName = "org/settings/options"
|
tplSettingsOptions templates.TplName = "org/settings/options"
|
||||||
// tplSettingsDelete template path for render delete repository
|
|
||||||
tplSettingsDelete templates.TplName = "org/settings/delete"
|
|
||||||
// tplSettingsHooks template path for render hook settings
|
// tplSettingsHooks template path for render hook settings
|
||||||
tplSettingsHooks templates.TplName = "org/settings/hooks"
|
tplSettingsHooks templates.TplName = "org/settings/hooks"
|
||||||
// tplSettingsLabels template path for render labels settings
|
// tplSettingsLabels template path for render labels settings
|
||||||
|
|
@ -71,26 +70,6 @@ func SettingsPost(ctx *context.Context) {
|
||||||
|
|
||||||
org := ctx.Org.Organization
|
org := ctx.Org.Organization
|
||||||
|
|
||||||
if org.Name != form.Name {
|
|
||||||
if err := user_service.RenameUser(ctx, org.AsUser(), form.Name); err != nil {
|
|
||||||
if user_model.IsErrUserAlreadyExist(err) {
|
|
||||||
ctx.Data["Err_Name"] = true
|
|
||||||
ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
|
|
||||||
} else if db.IsErrNameReserved(err) {
|
|
||||||
ctx.Data["Err_Name"] = true
|
|
||||||
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
|
|
||||||
} else if db.IsErrNamePatternNotAllowed(err) {
|
|
||||||
ctx.Data["Err_Name"] = true
|
|
||||||
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("RenameUser", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(org.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.Email != "" {
|
if form.Email != "" {
|
||||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
|
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
|
||||||
ctx.Data["Err_Email"] = true
|
ctx.Data["Err_Email"] = true
|
||||||
|
|
@ -163,42 +142,27 @@ func SettingsDeleteAvatar(ctx *context.Context) {
|
||||||
ctx.JSONRedirect(ctx.Org.OrgLink + "/settings")
|
ctx.JSONRedirect(ctx.Org.OrgLink + "/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingsDelete response for deleting an organization
|
// SettingsDeleteOrgPost response for deleting an organization
|
||||||
func SettingsDelete(ctx *context.Context) {
|
func SettingsDeleteOrgPost(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("org.settings")
|
if ctx.Org.Organization.Name != ctx.FormString("org_name") {
|
||||||
ctx.Data["PageIsOrgSettings"] = true
|
ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
|
||||||
ctx.Data["PageIsSettingsDelete"] = true
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Req.Method == http.MethodPost {
|
if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false /* no purge */); err != nil {
|
||||||
if ctx.Org.Organization.Name != ctx.FormString("org_name") {
|
if repo_model.IsErrUserOwnRepos(err) {
|
||||||
ctx.Data["Err_OrgName"] = true
|
ctx.JSONError(ctx.Tr("form.org_still_own_repo"))
|
||||||
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_org_name"), tplSettingsDelete, nil)
|
} else if packages_model.IsErrUserOwnPackages(err) {
|
||||||
return
|
ctx.JSONError(ctx.Tr("form.org_still_own_packages"))
|
||||||
}
|
|
||||||
|
|
||||||
if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
|
|
||||||
if repo_model.IsErrUserOwnRepos(err) {
|
|
||||||
ctx.Flash.Error(ctx.Tr("form.org_still_own_repo"))
|
|
||||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
|
|
||||||
} else if packages_model.IsErrUserOwnPackages(err) {
|
|
||||||
ctx.Flash.Error(ctx.Tr("form.org_still_own_packages"))
|
|
||||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("DeleteOrganization", err)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.Trace("Organization deleted: %s", ctx.Org.Organization.Name)
|
log.Error("DeleteOrganization: %v", err)
|
||||||
ctx.Redirect(setting.AppSubURL + "/")
|
ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.delete_failed"))))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
ctx.Flash.Success(ctx.Tr("org.settings.delete_successful", ctx.Org.Organization.Name))
|
||||||
ctx.ServerError("RenderUserOrgHeader", err)
|
ctx.JSONRedirect(setting.AppSubURL + "/")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSettingsDelete)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Webhooks render webhook list page
|
// Webhooks render webhook list page
|
||||||
|
|
@ -250,3 +214,40 @@ func Labels(ctx *context.Context) {
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSettingsLabels)
|
ctx.HTML(http.StatusOK, tplSettingsLabels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SettingsRenamePost response for renaming organization
|
||||||
|
func SettingsRenamePost(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.RenameOrgForm)
|
||||||
|
if ctx.HasError() {
|
||||||
|
ctx.JSONError(ctx.GetErrMsg())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oldOrgName, newOrgName := ctx.Org.Organization.Name, form.NewOrgName
|
||||||
|
|
||||||
|
if form.OrgName != oldOrgName {
|
||||||
|
ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newOrgName == oldOrgName {
|
||||||
|
ctx.JSONError(ctx.Tr("org.settings.rename_no_change"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := user_service.RenameUser(ctx, ctx.Org.Organization.AsUser(), newOrgName); err != nil {
|
||||||
|
if user_model.IsErrUserAlreadyExist(err) {
|
||||||
|
ctx.JSONError(ctx.Tr("org.form.name_been_taken", newOrgName))
|
||||||
|
} else if db.IsErrNameReserved(err) {
|
||||||
|
ctx.JSONError(ctx.Tr("org.form.name_reserved", newOrgName))
|
||||||
|
} else if db.IsErrNamePatternNotAllowed(err) {
|
||||||
|
ctx.JSONError(ctx.Tr("org.form.name_pattern_not_allowed", newOrgName))
|
||||||
|
} else {
|
||||||
|
log.Error("RenameOrganization: %v", err)
|
||||||
|
ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed"))))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("org.settings.rename_success", oldOrgName, newOrgName))
|
||||||
|
ctx.JSONRedirect(setting.AppSubURL + "/org/" + url.PathEscape(newOrgName) + "/settings")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -964,7 +964,8 @@ func registerWebRoutes(m *web.Router) {
|
||||||
addSettingsVariablesRoutes()
|
addSettingsVariablesRoutes()
|
||||||
}, actions.MustEnableActions)
|
}, actions.MustEnableActions)
|
||||||
|
|
||||||
m.Methods("GET,POST", "/delete", org.SettingsDelete)
|
m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRenamePost)
|
||||||
|
m.Post("/delete", org.SettingsDeleteOrgPost)
|
||||||
|
|
||||||
m.Group("/packages", func() {
|
m.Group("/packages", func() {
|
||||||
m.Get("", org.Packages)
|
m.Get("", org.Packages)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding
|
||||||
|
|
||||||
// UpdateOrgSettingForm form for updating organization settings
|
// UpdateOrgSettingForm form for updating organization settings
|
||||||
type UpdateOrgSettingForm struct {
|
type UpdateOrgSettingForm struct {
|
||||||
Name string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
|
|
||||||
FullName string `binding:"MaxSize(100)"`
|
FullName string `binding:"MaxSize(100)"`
|
||||||
Email string `binding:"MaxSize(255)"`
|
Email string `binding:"MaxSize(255)"`
|
||||||
Description string `binding:"MaxSize(255)"`
|
Description string `binding:"MaxSize(255)"`
|
||||||
|
|
@ -53,6 +52,11 @@ func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors)
|
||||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RenameOrgForm struct {
|
||||||
|
OrgName string `binding:"Required"`
|
||||||
|
NewOrgName string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
|
||||||
|
}
|
||||||
|
|
||||||
// ___________
|
// ___________
|
||||||
// \__ ___/___ _____ _____
|
// \__ ___/___ _____ _____
|
||||||
// | |_/ __ \\__ \ / \
|
// | |_/ __ \\__ \ / \
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings delete")}}
|
|
||||||
|
|
||||||
<div class="org-setting-content">
|
|
||||||
<h4 class="ui top attached error header">
|
|
||||||
{{ctx.Locale.Tr "org.settings.delete_account"}}
|
|
||||||
</h4>
|
|
||||||
<div class="ui attached error segment">
|
|
||||||
<div class="ui red message">
|
|
||||||
<p class="text left">{{svg "octicon-alert"}} {{ctx.Locale.Tr "org.settings.delete_prompt"}}</p>
|
|
||||||
</div>
|
|
||||||
<form class="ui form ignore-dirty" id="delete-form" action="{{.Link}}" method="post">
|
|
||||||
{{.CsrfTokenHtml}}
|
|
||||||
<div class="inline required field {{if .Err_OrgName}}error{{end}}">
|
|
||||||
<label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}}</label>
|
|
||||||
<input id="org_name" name="org_name" value="" autocomplete="off" autofocus required>
|
|
||||||
</div>
|
|
||||||
<button class="ui red button delete-button" data-type="form" data-form="#delete-form">
|
|
||||||
{{ctx.Locale.Tr "org.settings.confirm_delete_account"}}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui g-modal-confirm delete modal">
|
|
||||||
<div class="header">
|
|
||||||
{{svg "octicon-trash"}}
|
|
||||||
{{ctx.Locale.Tr "org.settings.delete_org_title"}}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<p>{{ctx.Locale.Tr "org.settings.delete_org_desc"}}</p>
|
|
||||||
</div>
|
|
||||||
{{template "base/modal_actions_confirm" .}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "org/settings/layout_footer" .}}
|
|
||||||
|
|
@ -41,8 +41,5 @@
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
{{end}}
|
{{end}}
|
||||||
<a class="{{if .PageIsSettingsDelete}}active {{end}}item" href="{{.OrgLink}}/settings/delete">
|
|
||||||
{{ctx.Locale.Tr "org.settings.delete"}}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,97 @@
|
||||||
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings options")}}
|
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings options")}}
|
||||||
<div class="org-setting-content">
|
|
||||||
<h4 class="ui top attached header">
|
|
||||||
{{ctx.Locale.Tr "org.settings.options"}}
|
|
||||||
</h4>
|
|
||||||
<div class="ui attached segment">
|
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
|
||||||
{{.CsrfTokenHtml}}
|
|
||||||
<div class="required field {{if .Err_Name}}error{{end}}">
|
|
||||||
<label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}}
|
|
||||||
<span class="text red tw-hidden" id="org-name-change-prompt">
|
|
||||||
<br>{{ctx.Locale.Tr "org.settings.change_orgname_prompt"}}<br>{{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" required maxlength="40">
|
|
||||||
</div>
|
|
||||||
<div class="field {{if .Err_FullName}}error{{end}}">
|
|
||||||
<label for="full_name">{{ctx.Locale.Tr "org.org_full_name_holder"}}</label>
|
|
||||||
<input id="full_name" name="full_name" value="{{.Org.FullName}}" maxlength="100">
|
|
||||||
</div>
|
|
||||||
<div class="field {{if .Err_Email}}error{{end}}">
|
|
||||||
<label for="email">{{ctx.Locale.Tr "org.settings.email"}}</label>
|
|
||||||
<input id="email" name="email" type="email" value="{{.Org.Email}}" maxlength="255">
|
|
||||||
</div>
|
|
||||||
<div class="field {{if .Err_Description}}error{{end}}">
|
|
||||||
{{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}}
|
|
||||||
<label for="description">{{ctx.Locale.Tr "org.org_desc"}}</label>
|
|
||||||
<textarea id="description" name="description" rows="2" maxlength="255">{{.Org.Description}}</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="field {{if .Err_Website}}error{{end}}">
|
|
||||||
<label for="website">{{ctx.Locale.Tr "org.settings.website"}}</label>
|
|
||||||
<input id="website" name="website" type="url" value="{{.Org.Website}}" maxlength="255">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="location">{{ctx.Locale.Tr "org.settings.location"}}</label>
|
|
||||||
<input id="location" name="location" value="{{.Org.Location}}" maxlength="50">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="ui segments org-setting-content">
|
||||||
<div class="field" id="visibility_box">
|
<h4 class="ui top attached header">
|
||||||
<label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
|
{{ctx.Locale.Tr "org.settings.options"}}
|
||||||
<div class="field">
|
</h4>
|
||||||
<div class="ui radio checkbox">
|
<div class="ui attached segment">
|
||||||
<input class="enable-system-radio" name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}>
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
|
{{.CsrfTokenHtml}}
|
||||||
</div>
|
<div class="field {{if .Err_FullName}}error{{end}}">
|
||||||
</div>
|
<label for="full_name">{{ctx.Locale.Tr "org.org_full_name_holder"}}</label>
|
||||||
<div class="field">
|
<input id="full_name" name="full_name" value="{{.Org.FullName}}" maxlength="100">
|
||||||
<div class="ui radio checkbox">
|
</div>
|
||||||
<input class="enable-system-radio" name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}>
|
<div class="field {{if .Err_Email}}error{{end}}">
|
||||||
<label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
|
<label for="email">{{ctx.Locale.Tr "org.settings.email"}}</label>
|
||||||
</div>
|
<input id="email" name="email" type="email" value="{{.Org.Email}}" maxlength="255">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field {{if .Err_Description}}error{{end}}">
|
||||||
<div class="ui radio checkbox">
|
{{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}}
|
||||||
<input class="enable-system-radio" name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}>
|
<label for="description">{{ctx.Locale.Tr "org.org_desc"}}</label>
|
||||||
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
|
<textarea id="description" name="description" rows="2" maxlength="255">{{.Org.Description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="field {{if .Err_Website}}error{{end}}">
|
||||||
</div>
|
<label for="website">{{ctx.Locale.Tr "org.settings.website"}}</label>
|
||||||
|
<input id="website" name="website" type="url" value="{{.Org.Website}}" maxlength="255">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="location">{{ctx.Locale.Tr "org.settings.location"}}</label>
|
||||||
|
<input id="location" name="location" value="{{.Org.Location}}" maxlength="50">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field" id="permission_box">
|
<div class="divider"></div>
|
||||||
<label>{{ctx.Locale.Tr "org.settings.permission"}}</label>
|
<div class="field" id="visibility_box">
|
||||||
<div class="field">
|
<label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
|
||||||
<div class="ui checkbox">
|
<div class="field">
|
||||||
<input type="checkbox" name="repo_admin_change_team_access" {{if .RepoAdminChangeTeamAccess}}checked{{end}}>
|
<div class="ui radio checkbox">
|
||||||
<label>{{ctx.Locale.Tr "org.settings.repoadminchangeteam"}}</label>
|
<input class="enable-system-radio" name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}>
|
||||||
</div>
|
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
{{if .SignedUser.IsAdmin}}
|
<div class="ui radio checkbox">
|
||||||
<div class="divider"></div>
|
<input class="enable-system-radio" name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
|
||||||
<div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}">
|
</div>
|
||||||
<label for="max_repo_creation">{{ctx.Locale.Tr "admin.users.max_repo_creation"}}</label>
|
</div>
|
||||||
<input id="max_repo_creation" name="max_repo_creation" type="number" min="-1" value="{{.Org.MaxRepoCreation}}">
|
<div class="field">
|
||||||
<p class="help">{{ctx.Locale.Tr "admin.users.max_repo_creation_desc"}}</p>
|
<div class="ui radio checkbox">
|
||||||
</div>
|
<input class="enable-system-radio" name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}>
|
||||||
{{end}}
|
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
|
||||||
|
</div>
|
||||||
<div class="field">
|
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "org.settings.update_settings"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
|
||||||
|
|
||||||
<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
|
|
||||||
{{.CsrfTokenHtml}}
|
|
||||||
<div class="inline field">
|
|
||||||
{{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}}
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button>
|
|
||||||
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field" id="permission_box">
|
||||||
|
<label>{{ctx.Locale.Tr "org.settings.permission"}}</label>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" name="repo_admin_change_team_access" {{if .RepoAdminChangeTeamAccess}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "org.settings.repoadminchangeteam"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .SignedUser.IsAdmin}}
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}">
|
||||||
|
<label for="max_repo_creation">{{ctx.Locale.Tr "admin.users.max_repo_creation"}}</label>
|
||||||
|
<input id="max_repo_creation" name="max_repo_creation" type="number" min="-1" value="{{.Org.MaxRepoCreation}}">
|
||||||
|
<p class="help">{{ctx.Locale.Tr "admin.users.max_repo_creation_desc"}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<button class="ui primary button">{{ctx.Locale.Tr "org.settings.update_settings"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div class="inline field">
|
||||||
|
{{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button>
|
||||||
|
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "org/settings/options_dangerzone" .}}
|
||||||
|
|
||||||
{{template "org/settings/layout_footer" .}}
|
{{template "org/settings/layout_footer" .}}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
<h4 class="ui top attached error header">
|
||||||
|
{{ctx.Locale.Tr "repo.settings.danger_zone"}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached error danger segment">
|
||||||
|
<div class="flex-list">
|
||||||
|
<div class="flex-item tw-items-center">
|
||||||
|
<div class="flex-item-main">
|
||||||
|
<div class="flex-item-title">{{ctx.Locale.Tr "org.settings.rename"}}</div>
|
||||||
|
<div class="flex-item-body">{{ctx.Locale.Tr "org.settings.rename_desc"}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-item-trailing">
|
||||||
|
<button class="ui basic red show-modal button" data-modal="#rename-org-modal">{{ctx.Locale.Tr "org.settings.rename"}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-item">
|
||||||
|
<div class="flex-item-main">
|
||||||
|
<div class="flex-item-title">{{ctx.Locale.Tr "org.settings.delete_account"}}</div>
|
||||||
|
<div class="flex-item-body">{{ctx.Locale.Tr "org.settings.delete_prompt"}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-item-trailing">
|
||||||
|
<button class="ui basic red show-modal button" data-modal="#delete-org-modal">{{ctx.Locale.Tr "org.settings.delete_account"}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui small modal" id="rename-org-modal">
|
||||||
|
<div class="header">
|
||||||
|
{{ctx.Locale.Tr "org.settings.rename"}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<ul class="ui warning message">
|
||||||
|
<li>{{ctx.Locale.Tr "org.settings.rename_notices_1"}}</li>
|
||||||
|
<li>{{ctx.Locale.Tr "org.settings.rename_notices_2"}}</li>
|
||||||
|
</ul>
|
||||||
|
<form class="ui form form-fetch-action" action="{{.Link}}/rename" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div class="field">
|
||||||
|
<label>
|
||||||
|
{{ctx.Locale.Tr "org.settings.name_confirm"}}
|
||||||
|
<span class="text red">{{.Org.Name}}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="required field">
|
||||||
|
<label for="org_name_to_rename">{{ctx.Locale.Tr "org.org_name_holder"}}</label>
|
||||||
|
<input id="org_name_to_rename" name="org_name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="required field">
|
||||||
|
<label>{{ctx.Locale.Tr "org.settings.rename_new_org_name"}}</label>
|
||||||
|
<input name="new_org_name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||||
|
<button class="ui red button">{{ctx.Locale.Tr "org.settings.rename"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui small modal" id="delete-org-modal">
|
||||||
|
<div class="header">
|
||||||
|
{{ctx.Locale.Tr "org.settings.delete_account"}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<ul class="ui warning message">
|
||||||
|
<li>{{ctx.Locale.Tr "org.settings.delete_notices_1"}}</li>
|
||||||
|
<li>{{ctx.Locale.Tr "org.settings.delete_notices_2" .Org.Name}}</li>
|
||||||
|
<li>{{ctx.Locale.Tr "org.settings.delete_notices_3" .Org.Name}}</li>
|
||||||
|
<li>{{ctx.Locale.Tr "org.settings.delete_notices_4" .Org.Name}}</li>
|
||||||
|
</ul>
|
||||||
|
<form class="ui form form-fetch-action" action="{{.Link}}/delete" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div class="field">
|
||||||
|
<label>
|
||||||
|
{{ctx.Locale.Tr "org.settings.name_confirm"}}
|
||||||
|
<span class="text red">{{.Org.Name}}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="required field">
|
||||||
|
<label>{{ctx.Locale.Tr "org.org_name_holder"}}</label>
|
||||||
|
<input name="org_name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||||
|
<button class="ui red button">{{ctx.Locale.Tr "org.settings.delete_account"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<!-- the "cup" has a handler, so move it a little leftward to make it visually in the center -->
|
<!-- the "cup" has a handler, so move it a little leftward to make it visually in the center -->
|
||||||
<div class="tw-ml-[-30px]"><img width="160" src="{{AssetUrlPrefix}}/img/loading.png" alt aria-hidden="true"></div>
|
<div class="tw-ml-[-30px]"><img width="160" src="{{AssetUrlPrefix}}/img/loading.png" alt aria-hidden="true"></div>
|
||||||
<div class="tw-my-[2em] tw-text-[18px]">
|
<div class="tw-my-[2em] tw-text-[18px]">
|
||||||
<a id="goto-user-login" href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "install.installing_desc"}}</a>
|
<a id="goto-after-install" href="{{AppSubUrl}}{{Iif .IsAccountCreated "/user/login" "/user/sign_up"}}">{{ctx.Locale.Tr "install.installing_desc"}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
|
{{if .IsFirstTimeRegistration}}
|
||||||
|
<p>{{ctx.Locale.Tr "auth.sign_up_tip"}}</p>
|
||||||
|
{{end}}
|
||||||
<form class="ui form" action="{{.SignUpLink}}" method="post">
|
<form class="ui form" action="{{.SignUpLink}}" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}}
|
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@
|
||||||
--page-spacing: 16px; /* space between page elements */
|
--page-spacing: 16px; /* space between page elements */
|
||||||
--page-margin-x: 32px; /* minimum space on left and right side of page */
|
--page-margin-x: 32px; /* minimum space on left and right side of page */
|
||||||
--page-space-bottom: 64px; /* space between last page element and footer */
|
--page-space-bottom: 64px; /* space between last page element and footer */
|
||||||
|
|
||||||
|
/* z-index */
|
||||||
|
--z-index-modal: 1001; /* modal dialog, hard-coded from Fomantic modal.css */
|
||||||
|
--z-index-toast: 1002; /* should be larger than modal */
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) and (max-width: 1200px) {
|
@media (min-width: 768px) and (max-width: 1200px) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.dimmer > * {
|
.ui.dimmer > .ui.modal {
|
||||||
position: static;
|
position: static;
|
||||||
margin-top: auto !important;
|
margin-top: auto !important;
|
||||||
margin-bottom: auto !important;
|
margin-bottom: auto !important;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all .2s ease;
|
transition: all .2s ease;
|
||||||
z-index: 500;
|
z-index: var(--z-index-toast);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
box-shadow: 0 8px 24px var(--color-shadow);
|
box-shadow: 0 8px 24px var(--color-shadow);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -525,7 +525,7 @@ $.fn.dropdown = function(parameters) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(settings.onShow.call(element) !== false) {
|
if(settings.onShow.call(element) !== false) {
|
||||||
settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
$module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
||||||
module.animate.show(function() {
|
module.animate.show(function() {
|
||||||
if( module.can.click() ) {
|
if( module.can.click() ) {
|
||||||
module.bind.intent();
|
module.bind.intent();
|
||||||
|
|
@ -753,7 +753,7 @@ $.fn.dropdown = function(parameters) {
|
||||||
if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
|
if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
|
||||||
module.show();
|
module.show();
|
||||||
}
|
}
|
||||||
settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
$module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
if(settings.useLabels && module.has.maxSelections()) {
|
if(settings.useLabels && module.has.maxSelections()) {
|
||||||
|
|
@ -3994,8 +3994,6 @@ $.fn.dropdown.settings = {
|
||||||
onShow : function(){},
|
onShow : function(){},
|
||||||
onHide : function(){},
|
onHide : function(){},
|
||||||
|
|
||||||
onAfterFiltered: function(){}, // GITEA-PATCH: callback to correctly handle the filtered items
|
|
||||||
|
|
||||||
/* Component */
|
/* Component */
|
||||||
name : 'Dropdown',
|
name : 'Dropdown',
|
||||||
namespace : 'dropdown',
|
namespace : 'dropdown',
|
||||||
|
|
|
||||||
|
|
@ -467,7 +467,7 @@ $.fn.modal = function(parameters) {
|
||||||
ignoreRepeatedEvents = false;
|
ignoreRepeatedEvents = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$module.fomanticExt.onModalBeforeHidden.call(element); // GITEA-PATCH: handle more UI updates before hidden
|
||||||
if( module.is.animating() || module.is.active() ) {
|
if( module.is.animating() || module.is.active() ) {
|
||||||
if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
|
if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
|
||||||
module.remove.active();
|
module.remove.active();
|
||||||
|
|
@ -641,7 +641,7 @@ $.fn.modal = function(parameters) {
|
||||||
$module
|
$module
|
||||||
.off('mousedown' + elementEventNamespace)
|
.off('mousedown' + elementEventNamespace)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
$dimmer
|
$dimmer
|
||||||
.off('mousedown' + elementEventNamespace)
|
.off('mousedown' + elementEventNamespace)
|
||||||
;
|
;
|
||||||
|
|
@ -877,7 +877,7 @@ $.fn.modal = function(parameters) {
|
||||||
? $(document).scrollTop() + settings.padding
|
? $(document).scrollTop() + settings.padding
|
||||||
: $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding),
|
: $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding),
|
||||||
marginLeft: -(module.cache.width / 2)
|
marginLeft: -(module.cache.width / 2)
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
} else {
|
} else {
|
||||||
$module
|
$module
|
||||||
|
|
@ -886,7 +886,7 @@ $.fn.modal = function(parameters) {
|
||||||
? -(module.cache.height / 2)
|
? -(module.cache.height / 2)
|
||||||
: settings.padding / 2,
|
: settings.padding / 2,
|
||||||
marginLeft: -(module.cache.width / 2)
|
marginLeft: -(module.cache.width / 2)
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
module.verbose('Setting modal offset for legacy mode');
|
module.verbose('Setting modal offset for legacy mode');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {request} from '../modules/fetch.ts';
|
import {request} from '../modules/fetch.ts';
|
||||||
import {showErrorToast} from '../modules/toast.ts';
|
import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
|
||||||
import {addDelegatedEventListener, submitEventSubmitter} from '../utils/dom.ts';
|
import {addDelegatedEventListener, submitEventSubmitter} from '../utils/dom.ts';
|
||||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||||
import type {RequestOpts} from '../types.ts';
|
import type {RequestOpts} from '../types.ts';
|
||||||
|
|
@ -24,6 +24,7 @@ function fetchActionDoRedirect(redirect: string) {
|
||||||
|
|
||||||
async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: RequestOpts) {
|
async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: RequestOpts) {
|
||||||
try {
|
try {
|
||||||
|
hideToastsAll();
|
||||||
const resp = await request(url, opt);
|
const resp = await request(url, opt);
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
let {redirect} = await resp.json();
|
let {redirect} = await resp.json();
|
||||||
|
|
@ -35,7 +36,9 @@ async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: R
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (resp.status >= 400 && resp.status < 500) {
|
}
|
||||||
|
|
||||||
|
if (resp.status >= 400 && resp.status < 500) {
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
|
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
|
||||||
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
|
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ function initPreInstall() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPostInstall() {
|
function initPostInstall() {
|
||||||
const el = document.querySelector('#goto-user-login');
|
const el = document.querySelector('#goto-after-install');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const targetUrl = el.getAttribute('href');
|
const targetUrl = el.getAttribute('href');
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ const fomanticDropdownFn = $.fn.dropdown;
|
||||||
// use our own `$().dropdown` function to patch Fomantic's dropdown module
|
// use our own `$().dropdown` function to patch Fomantic's dropdown module
|
||||||
export function initAriaDropdownPatch() {
|
export function initAriaDropdownPatch() {
|
||||||
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
|
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
|
||||||
$.fn.dropdown.settings.onAfterFiltered = onAfterFiltered;
|
|
||||||
$.fn.dropdown = ariaDropdownFn;
|
$.fn.dropdown = ariaDropdownFn;
|
||||||
$.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem;
|
$.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem;
|
||||||
|
$.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered;
|
||||||
(ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings;
|
(ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ function updateSelectionLabel(label: HTMLElement) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAfterFiltered(this: any) {
|
function onDropdownAfterFiltered(this: any) {
|
||||||
const $dropdown = $(this).closest('.ui.dropdown'); // "this" can be the "ui dropdown" or "<select>"
|
const $dropdown = $(this).closest('.ui.dropdown'); // "this" can be the "ui dropdown" or "<select>"
|
||||||
const hideEmptyDividers = $dropdown.dropdown('setting', 'hideDividers') === 'empty';
|
const hideEmptyDividers = $dropdown.dropdown('setting', 'hideDividers') === 'empty';
|
||||||
const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu');
|
const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import type {FomanticInitFunction} from '../../types.ts';
|
import type {FomanticInitFunction} from '../../types.ts';
|
||||||
|
import {queryElems} from '../../utils/dom.ts';
|
||||||
|
import {hideToastsFrom} from '../toast.ts';
|
||||||
|
|
||||||
const fomanticModalFn = $.fn.modal;
|
const fomanticModalFn = $.fn.modal;
|
||||||
|
|
||||||
|
|
@ -7,6 +9,7 @@ const fomanticModalFn = $.fn.modal;
|
||||||
export function initAriaModalPatch() {
|
export function initAriaModalPatch() {
|
||||||
if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once');
|
if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once');
|
||||||
$.fn.modal = ariaModalFn;
|
$.fn.modal = ariaModalFn;
|
||||||
|
$.fn.fomanticExt.onModalBeforeHidden = onModalBeforeHidden;
|
||||||
(ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings;
|
(ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,3 +30,10 @@ function ariaModalFn(this: any, ...args: Parameters<FomanticInitFunction>) {
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onModalBeforeHidden(this: any) {
|
||||||
|
const $modal = $(this);
|
||||||
|
const elModal = $modal[0];
|
||||||
|
queryElems(elModal, 'form', (form: HTMLFormElement) => form.reset());
|
||||||
|
hideToastsFrom(elModal.closest('.ui.dimmer') ?? document.body);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {htmlEscape} from 'escape-goat';
|
import {htmlEscape} from 'escape-goat';
|
||||||
import {svg} from '../svg.ts';
|
import {svg} from '../svg.ts';
|
||||||
import {animateOnce, showElem} from '../utils/dom.ts';
|
import {animateOnce, queryElems, showElem} from '../utils/dom.ts';
|
||||||
import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown
|
import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown
|
||||||
import type {Intent} from '../types.ts';
|
import type {Intent} from '../types.ts';
|
||||||
import type {SvgName} from '../svg.ts';
|
import type {SvgName} from '../svg.ts';
|
||||||
|
|
@ -37,17 +37,20 @@ const levels: ToastLevels = {
|
||||||
|
|
||||||
type ToastOpts = {
|
type ToastOpts = {
|
||||||
useHtmlBody?: boolean,
|
useHtmlBody?: boolean,
|
||||||
preventDuplicates?: boolean,
|
preventDuplicates?: boolean | string,
|
||||||
} & Options;
|
} & Options;
|
||||||
|
|
||||||
|
type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast };
|
||||||
|
|
||||||
// See https://github.com/apvarun/toastify-js#api for options
|
// See https://github.com/apvarun/toastify-js#api for options
|
||||||
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast {
|
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast {
|
||||||
const body = useHtmlBody ? String(message) : htmlEscape(message);
|
const body = useHtmlBody ? String(message) : htmlEscape(message);
|
||||||
const key = `${level}-${body}`;
|
const parent = document.querySelector('.ui.dimmer.active') ?? document.body;
|
||||||
|
const duplicateKey = preventDuplicates ? (preventDuplicates === true ? `${level}-${body}` : preventDuplicates) : '';
|
||||||
|
|
||||||
// prevent showing duplicate toasts with same level and message, and give a visual feedback for end users
|
// prevent showing duplicate toasts with the same level and message, and give visual feedback for end users
|
||||||
if (preventDuplicates) {
|
if (preventDuplicates) {
|
||||||
const toastEl = document.querySelector(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`);
|
const toastEl = parent.querySelector(`:scope > .toastify.on[data-toast-unique-key="${CSS.escape(duplicateKey)}"]`);
|
||||||
if (toastEl) {
|
if (toastEl) {
|
||||||
const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number');
|
const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number');
|
||||||
showElem(toastDupNumEl);
|
showElem(toastDupNumEl);
|
||||||
|
|
@ -59,6 +62,7 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
||||||
|
|
||||||
const {icon, background, duration: levelDuration} = levels[level ?? 'info'];
|
const {icon, background, duration: levelDuration} = levels[level ?? 'info'];
|
||||||
const toast = Toastify({
|
const toast = Toastify({
|
||||||
|
selector: parent,
|
||||||
text: `
|
text: `
|
||||||
<div class='toast-icon'>${svg(icon)}</div>
|
<div class='toast-icon'>${svg(icon)}</div>
|
||||||
<div class='toast-body'><span class="toast-duplicate-number tw-hidden">1</span>${body}</div>
|
<div class='toast-body'><span class="toast-duplicate-number tw-hidden">1</span>${body}</div>
|
||||||
|
|
@ -74,7 +78,8 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
||||||
|
|
||||||
toast.showToast();
|
toast.showToast();
|
||||||
toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
|
toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
|
||||||
toast.toastElement.setAttribute('data-toast-unique-key', key);
|
toast.toastElement.setAttribute('data-toast-unique-key', duplicateKey);
|
||||||
|
(toast.toastElement as ToastifyElement)._giteaToastifyInstance = toast;
|
||||||
return toast;
|
return toast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,3 +94,15 @@ export function showWarningToast(message: string, opts?: ToastOpts): Toast {
|
||||||
export function showErrorToast(message: string, opts?: ToastOpts): Toast {
|
export function showErrorToast(message: string, opts?: ToastOpts): Toast {
|
||||||
return showToast(message, 'error', opts);
|
return showToast(message, 'error', opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideToastByElement(el: Element): void {
|
||||||
|
(el as ToastifyElement)?._giteaToastifyInstance?.hideToast();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideToastsFrom(parent: Element): void {
|
||||||
|
queryElems(parent, ':scope > .toastify.on', hideToastByElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideToastsAll(): void {
|
||||||
|
queryElems(document, '.toastify.on', hideToastByElement);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue