diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 123c2da607ec5..e94e0ab9ca9cf 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2811,6 +2811,7 @@ team_permission_desc = Permission
team_unit_desc = Allow Access to Repository Sections
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_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.
@@ -2832,15 +2833,28 @@ settings.visibility.private_shortname = Private
settings.update_settings = Update Settings
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 CANNOT 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.delete = Delete Organization
settings.delete_account = Delete This Organization
settings.delete_prompt = The organization will be permanently removed. This CANNOT be undone!
+settings.name_confirm = Enter the organization name as confirmation:
+settings.delete_notices_1 = This operation CANNOT be undone.
+settings.delete_notices_2 = This operation will permanently delete all the repositories of %s including code, issues, comments, wiki data and collaborator settings.
+settings.delete_notices_3 = This operation will permanently delete all the packages of %s.
+settings.delete_notices_4 = This operation will permanently delete all the projects of %s.
settings.confirm_delete_account = Confirm Deletion
-settings.delete_org_title = Delete Organization
-settings.delete_org_desc = This organization will be deleted permanently. Continue?
+settings.delete_failed = Delete Organization failed because of internal error
+settings.delete_successful = Organization %s has been deleted successfully.
settings.hooks_desc = Add webhooks which will be triggered for all repositories under this organization.
settings.labels_desc = Add labels which can be used on issues for all repositories under this organization.
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index 9dd0a98160e60..2bc1e8bc43388 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -18,6 +18,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
@@ -31,8 +32,6 @@ import (
const (
// tplSettingsOptions template path for render settings
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 templates.TplName = "org/settings/hooks"
// tplSettingsLabels template path for render labels settings
@@ -71,26 +70,6 @@ func SettingsPost(ctx *context.Context) {
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 err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
ctx.Data["Err_Email"] = true
@@ -163,42 +142,27 @@ func SettingsDeleteAvatar(ctx *context.Context) {
ctx.JSONRedirect(ctx.Org.OrgLink + "/settings")
}
-// SettingsDelete response for deleting an organization
-func SettingsDelete(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("org.settings")
- ctx.Data["PageIsOrgSettings"] = true
- ctx.Data["PageIsSettingsDelete"] = true
-
- if ctx.Req.Method == http.MethodPost {
- if ctx.Org.Organization.Name != ctx.FormString("org_name") {
- ctx.Data["Err_OrgName"] = true
- ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_org_name"), tplSettingsDelete, nil)
- return
- }
-
- 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 {
- log.Trace("Organization deleted: %s", ctx.Org.Organization.Name)
- ctx.Redirect(setting.AppSubURL + "/")
- }
+// SettingsDeleteOrgPost response for deleting an organization
+func SettingsDeleteOrgPost(ctx *context.Context) {
+ if ctx.Org.Organization.Name != ctx.FormString("org_name") {
+ ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
return
}
- if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
- ctx.ServerError("RenderUserOrgHeader", err)
+ if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false /* no purge */); err != nil {
+ if repo_model.IsErrUserOwnRepos(err) {
+ ctx.JSONError(ctx.Tr("form.org_still_own_repo"))
+ } else if packages_model.IsErrUserOwnPackages(err) {
+ ctx.JSONError(ctx.Tr("form.org_still_own_packages"))
+ } else {
+ log.Error("DeleteOrganization: %v", err)
+ ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.delete_failed"))))
+ }
return
}
- ctx.HTML(http.StatusOK, tplSettingsDelete)
+ ctx.Flash.Success(ctx.Tr("org.settings.delete_successful", ctx.Org.Organization.Name))
+ ctx.JSONRedirect(setting.AppSubURL + "/")
}
// Webhooks render webhook list page
@@ -250,3 +214,40 @@ func Labels(ctx *context.Context) {
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")
+}
diff --git a/routers/web/web.go b/routers/web/web.go
index 4012231c4b712..3040375def2c6 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -964,7 +964,8 @@ func registerWebRoutes(m *web.Router) {
addSettingsVariablesRoutes()
}, 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.Get("", org.Packages)
diff --git a/services/forms/org.go b/services/forms/org.go
index db182f7e96b5b..2ac18ef25cc63 100644
--- a/services/forms/org.go
+++ b/services/forms/org.go
@@ -36,7 +36,6 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding
// UpdateOrgSettingForm form for updating organization settings
type UpdateOrgSettingForm struct {
- Name string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
FullName string `binding:"MaxSize(100)"`
Email 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)
}
+type RenameOrgForm struct {
+ OrgName string `binding:"Required"`
+ NewOrgName string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
+}
+
// ___________
// \__ ___/___ _____ _____
// | |_/ __ \\__ \ / \
diff --git a/templates/org/settings/delete.tmpl b/templates/org/settings/delete.tmpl
deleted file mode 100644
index e1ef471e34124..0000000000000
--- a/templates/org/settings/delete.tmpl
+++ /dev/null
@@ -1,35 +0,0 @@
-{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings delete")}}
-
-
-
-
-
-
{{svg "octicon-alert"}} {{ctx.Locale.Tr "org.settings.delete_prompt"}}
-
-
-
-
-
-
-
-
-
{{ctx.Locale.Tr "org.settings.delete_org_desc"}}
-
- {{template "base/modal_actions_confirm" .}}
-
-
-{{template "org/settings/layout_footer" .}}
diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl
index ce792f667c4f9..58475de7e7a31 100644
--- a/templates/org/settings/navbar.tmpl
+++ b/templates/org/settings/navbar.tmpl
@@ -41,8 +41,5 @@
{{end}}
-
- {{ctx.Locale.Tr "org.settings.delete"}}
-
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl
index f4583bbe366b6..d94bb4c62b2e5 100644
--- a/templates/org/settings/options.tmpl
+++ b/templates/org/settings/options.tmpl
@@ -1,101 +1,97 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings options")}}
-
-
-
+
+{{template "org/settings/options_dangerzone" .}}
+
{{template "org/settings/layout_footer" .}}
diff --git a/templates/org/settings/options_dangerzone.tmpl b/templates/org/settings/options_dangerzone.tmpl
new file mode 100644
index 0000000000000..01cf3fd4051dc
--- /dev/null
+++ b/templates/org/settings/options_dangerzone.tmpl
@@ -0,0 +1,93 @@
+
+
+
+
+
+
{{ctx.Locale.Tr "org.settings.rename"}}
+
{{ctx.Locale.Tr "org.settings.rename_desc"}}
+
+
+
+
+
+
+
+
+
{{ctx.Locale.Tr "org.settings.delete_account"}}
+
{{ctx.Locale.Tr "org.settings.delete_prompt"}}
+
+
+
+
+
+
+
+
+
+
+
+
+ - {{ctx.Locale.Tr "org.settings.rename_notices_1"}}
+ - {{ctx.Locale.Tr "org.settings.rename_notices_2"}}
+
+
+
+
+
+
+
+
+
+ - {{ctx.Locale.Tr "org.settings.delete_notices_1"}}
+ - {{ctx.Locale.Tr "org.settings.delete_notices_2" .Org.Name}}
+ - {{ctx.Locale.Tr "org.settings.delete_notices_3" .Org.Name}}
+ - {{ctx.Locale.Tr "org.settings.delete_notices_4" .Org.Name}}
+
+
+
+
diff --git a/web_src/css/base.css b/web_src/css/base.css
index b50abf79f1d2c..dc58fb850a134 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -30,6 +30,10 @@
--page-spacing: 16px; /* space between page elements */
--page-margin-x: 32px; /* minimum space on left and right side of page */
--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) {
diff --git a/web_src/css/modules/dimmer.css b/web_src/css/modules/dimmer.css
index 89248213707e3..7d1ca6171aa9e 100644
--- a/web_src/css/modules/dimmer.css
+++ b/web_src/css/modules/dimmer.css
@@ -20,7 +20,7 @@
opacity: 1;
}
-.ui.dimmer > * {
+.ui.dimmer > .ui.modal {
position: static;
margin-top: auto !important;
margin-bottom: auto !important;
diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css
index 1145f3b1b58be..330d3b176eb39 100644
--- a/web_src/css/modules/toast.css
+++ b/web_src/css/modules/toast.css
@@ -3,7 +3,7 @@
position: fixed;
opacity: 0;
transition: all .2s ease;
- z-index: 500;
+ z-index: var(--z-index-toast);
border-radius: var(--border-radius);
box-shadow: 0 8px 24px var(--color-shadow);
display: flex;
diff --git a/web_src/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js
index 85530c79912ee..3ad098486516b 100644
--- a/web_src/fomantic/build/components/dropdown.js
+++ b/web_src/fomantic/build/components/dropdown.js
@@ -525,7 +525,7 @@ $.fn.dropdown = function(parameters) {
return true;
}
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() {
if( module.can.click() ) {
module.bind.intent();
@@ -753,7 +753,7 @@ $.fn.dropdown = function(parameters) {
if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
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()) {
@@ -3994,8 +3994,6 @@ $.fn.dropdown.settings = {
onShow : function(){},
onHide : function(){},
- onAfterFiltered: function(){}, // GITEA-PATCH: callback to correctly handle the filtered items
-
/* Component */
name : 'Dropdown',
namespace : 'dropdown',
diff --git a/web_src/fomantic/build/components/modal.js b/web_src/fomantic/build/components/modal.js
index 420ecc250baa1..3f578ccfccc7d 100644
--- a/web_src/fomantic/build/components/modal.js
+++ b/web_src/fomantic/build/components/modal.js
@@ -467,7 +467,7 @@ $.fn.modal = function(parameters) {
ignoreRepeatedEvents = false;
return false;
}
-
+ $module.fomanticExt.onModalBeforeHidden.call(element); // GITEA-PATCH: handle more UI updates before hidden
if( module.is.animating() || module.is.active() ) {
if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
module.remove.active();
@@ -641,7 +641,7 @@ $.fn.modal = function(parameters) {
$module
.off('mousedown' + elementEventNamespace)
;
- }
+ }
$dimmer
.off('mousedown' + elementEventNamespace)
;
@@ -877,7 +877,7 @@ $.fn.modal = function(parameters) {
? $(document).scrollTop() + settings.padding
: $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding),
marginLeft: -(module.cache.width / 2)
- })
+ })
;
} else {
$module
@@ -886,7 +886,7 @@ $.fn.modal = function(parameters) {
? -(module.cache.height / 2)
: settings.padding / 2,
marginLeft: -(module.cache.width / 2)
- })
+ })
;
}
module.verbose('Setting modal offset for legacy mode');
diff --git a/web_src/js/features/common-fetch-action.ts b/web_src/js/features/common-fetch-action.ts
index 5b6430779fd06..93597134540d8 100644
--- a/web_src/js/features/common-fetch-action.ts
+++ b/web_src/js/features/common-fetch-action.ts
@@ -1,5 +1,5 @@
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 {confirmModal} from './comp/ConfirmModal.ts';
import type {RequestOpts} from '../types.ts';
@@ -24,6 +24,7 @@ function fetchActionDoRedirect(redirect: string) {
async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: RequestOpts) {
try {
+ hideToastsAll();
const resp = await request(url, opt);
if (resp.status === 200) {
let {redirect} = await resp.json();
@@ -35,7 +36,9 @@ async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: R
window.location.reload();
}
return;
- } else if (resp.status >= 400 && resp.status < 500) {
+ }
+
+ if (resp.status >= 400 && resp.status < 500) {
const data = await resp.json();
// 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.
diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts
index 02fee5a267d85..ccc22073d7fe5 100644
--- a/web_src/js/modules/fomantic/dropdown.ts
+++ b/web_src/js/modules/fomantic/dropdown.ts
@@ -9,9 +9,9 @@ const fomanticDropdownFn = $.fn.dropdown;
// use our own `$().dropdown` function to patch Fomantic's dropdown module
export function initAriaDropdownPatch() {
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
- $.fn.dropdown.settings.onAfterFiltered = onAfterFiltered;
$.fn.dropdown = ariaDropdownFn;
$.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem;
+ $.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered;
(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 "