Add modals to Organization and Team remove/leave (#16471)

* Add modals to Organization and Team remove/leave

Add confirmation modals to Organization and Team remove and leave.

Fix #16215

Signed-off-by: Andrew Thornton <art27@cantab.net>

* avoid for-in

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Revert "avoid for-in"

This reverts commit 2af9a6f9d4.

* Apply suggestions from code review

Co-authored-by: silverwind <me@silverwind.io>

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
zeripath 2021-08-27 03:57:40 +01:00 committed by GitHub
parent 3289aceec2
commit e37342db0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 157 additions and 40 deletions

View File

@ -2176,12 +2176,15 @@ members.member_role = Member Role:
members.owner = Owner members.owner = Owner
members.member = Member members.member = Member
members.remove = Remove members.remove = Remove
members.remove.detail = Remove %[1]s from %[2]s?
members.leave = Leave members.leave = Leave
members.leave.detail = Leave %s?
members.invite_desc = Add a new member to %s: members.invite_desc = Add a new member to %s:
members.invite_now = Invite Now members.invite_now = Invite Now
teams.join = Join teams.join = Join
teams.leave = Leave teams.leave = Leave
teams.leave.detail = Leave %s?
teams.can_create_org_repo = Create repositories teams.can_create_org_repo = Create repositories
teams.can_create_org_repo_helper = Members can create new repositories in organization. Creator will get administrator access to the new repository. teams.can_create_org_repo_helper = Members can create new repositories in organization. Creator will get administrator access to the new repository.
teams.read_access = Read Access teams.read_access = Read Access

View File

@ -99,14 +99,18 @@ func MembersAction(ctx *context.Context) {
err = org.RemoveMember(uid) err = org.RemoveMember(uid)
if models.IsErrLastOrgOwner(err) { if models.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner")) ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
ctx.Redirect(ctx.Org.OrgLink + "/members") ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": ctx.Org.OrgLink + "/members",
})
return return
} }
case "leave": case "leave":
err = org.RemoveMember(ctx.User.ID) err = org.RemoveMember(ctx.User.ID)
if models.IsErrLastOrgOwner(err) { if models.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner")) ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
ctx.Redirect(ctx.Org.OrgLink + "/members") ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": ctx.Org.OrgLink + "/members",
})
return return
} }
} }
@ -120,9 +124,12 @@ func MembersAction(ctx *context.Context) {
return return
} }
if ctx.Params(":action") != "leave" { redirect := ctx.Org.OrgLink + "/members"
ctx.Redirect(ctx.Org.OrgLink + "/members") if ctx.Params(":action") == "leave" {
} else { redirect = setting.AppSubURL + "/"
ctx.Redirect(setting.AppSubURL + "/")
} }
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": redirect,
})
} }

View File

@ -66,6 +66,23 @@ func TeamsAction(ctx *context.Context) {
err = ctx.Org.Team.AddMember(ctx.User.ID) err = ctx.Org.Team.AddMember(ctx.User.ID)
case "leave": case "leave":
err = ctx.Org.Team.RemoveMember(ctx.User.ID) err = ctx.Org.Team.RemoveMember(ctx.User.ID)
if err != nil {
if models.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
} else {
log.Error("Action(%s): %v", ctx.Params(":action"), err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"ok": false,
"err": err.Error(),
})
return
}
}
ctx.JSON(http.StatusOK,
map[string]interface{}{
"redirect": ctx.Org.OrgLink + "/teams/",
})
return
case "remove": case "remove":
if !ctx.Org.IsOwner { if !ctx.Org.IsOwner {
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)
@ -73,6 +90,23 @@ func TeamsAction(ctx *context.Context) {
} }
err = ctx.Org.Team.RemoveMember(uid) err = ctx.Org.Team.RemoveMember(uid)
page = "team" page = "team"
if err != nil {
if models.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
} else {
log.Error("Action(%s): %v", ctx.Params(":action"), err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"ok": false,
"err": err.Error(),
})
return
}
}
ctx.JSON(http.StatusOK,
map[string]interface{}{
"redirect": ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName,
})
return
case "add": case "add":
if !ctx.Org.IsOwner { if !ctx.Org.IsOwner {
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)

View File

@ -54,14 +54,18 @@
<div class="ui four wide column"> <div class="ui four wide column">
<div class="text right"> <div class="text right">
{{if eq $.SignedUser.ID .ID}} {{if eq $.SignedUser.ID .ID}}
<form method="post" action="{{$.OrgLink}}/members/action/leave"> <form>
{{$.CsrfTokenHtml}} <button class="ui red small button delete-button" data-modal-id="leave-organization"
<button type="submit" class="ui red small button" name="uid" value="{{.ID}}">{{$.i18n.Tr "org.members.leave"}}</button> data-url="{{$.OrgLink}}/members/action/leave" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}"
data-data-organization-name="{{$.Org.DisplayName}}">{{$.i18n.Tr "org.members.leave"}}</button>
</form> </form>
{{else if $.IsOrganizationOwner}} {{else if $.IsOrganizationOwner}}
<form method="post" action="{{$.OrgLink}}/members/action/remove"> <form>
{{$.CsrfTokenHtml}} <button class="ui red small button delete-button" data-modal-id="remove-organization-member"
<button type="submit" class="ui red small button" name="uid" value="{{.ID}}">{{$.i18n.Tr "org.members.remove"}}</button> data-url="{{$.OrgLink}}/members/action/remove" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}"
data-data-organization-name="{{$.Org.DisplayName}}">{{$.i18n.Tr "org.members.remove"}}</button>
</form> </form>
{{end}} {{end}}
</div> </div>
@ -73,4 +77,25 @@
{{template "base/paginate" .}} {{template "base/paginate" .}}
</div> </div>
</div> </div>
<div class="ui small basic delete modal" id="leave-organization">
<div class="ui icon header">
{{svg "octicon-x" 16 "close inside"}}
{{$.i18n.Tr "org.members.leave"}}
</div>
<div class="content">
<p>{{$.i18n.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>
<div class="ui small basic delete modal" id="remove-organization-member">
<div class="ui icon header">
{{svg "octicon-x" 16 "close inside"}}
{{$.i18n.Tr "org.members.remove"}}
</div>
<div class="content">
<p>{{$.i18n.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataOrganizationName"></span>` | Safe}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@ -26,10 +26,12 @@
<div class="ui bottom attached table segment members"> <div class="ui bottom attached table segment members">
{{range .Team.Members}} {{range .Team.Members}}
<div class="item"> <div class="item">
{{if $.IsOrganizationOwner}} {{if and $.IsOrganizationOwner (not (eq $.SignedUser.ID .ID))}}
<form method="post" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/remove"> <form>
{{$.CsrfTokenHtml}} <button class="ui red button delete-button right" data-modal-id="remove-team-member"
<button type="submit" class="ui red small button right" name="uid" value="{{.ID}}">{{$.i18n.Tr "org.members.remove"}}</button> data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/remove" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}"
data-data-team-name="{{$.Team.Name}}">{{$.i18n.Tr "org.members.remove"}}</button>
</form> </form>
{{end}} {{end}}
<a href="{{.HomeLink}}"> <a href="{{.HomeLink}}">
@ -47,4 +49,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui small basic delete modal" id="remove-team-member">
<div class="ui icon header">
{{svg "octicon-x" 16 "close inside"}}
{{$.i18n.Tr "org.members.remove"}}
</div>
<div class="content">
<p>{{$.i18n.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataTeamName"></span>` | Safe}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@ -3,10 +3,10 @@
<strong>{{.Team.Name}}</strong> <strong>{{.Team.Name}}</strong>
<div class="ui right"> <div class="ui right">
{{if .Team.IsMember $.SignedUser.ID}} {{if .Team.IsMember $.SignedUser.ID}}
<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/leave"> <form>
{{$.CsrfTokenHtml}} <button class="ui red tiny button delete-button" data-modal-id="leave-team-sidebar"
<input type="hidden" name="page" value="home"/> data-url="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
<button type="submit" class="ui red tiny button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.leave"}}</button> data-name="{{.Team.Name}}">{{$.i18n.Tr "org.teams.leave"}}</button>
</form> </form>
{{else if .IsOrganizationOwner}} {{else if .IsOrganizationOwner}}
<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/join"> <form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/join">
@ -59,3 +59,13 @@
</div> </div>
{{end}} {{end}}
</div> </div>
<div class="ui small basic delete modal" id="leave-team-sidebar">
<div class="ui icon header">
{{svg "octicon-x" 16 "close inside"}}
{{$.i18n.Tr "org.teams.leave"}}
</div>
<div class="content">
<p>{{$.i18n.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

View File

@ -17,9 +17,10 @@
<a class="text black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a> <a class="text black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a>
<div class="ui right"> <div class="ui right">
{{if .IsMember $.SignedUser.ID}} {{if .IsMember $.SignedUser.ID}}
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave"> <form>
{{$.CsrfTokenHtml}} <button class="ui red tiny button delete-button" data-modal-id="leave-team"
<button type="submit" class="ui red small button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.leave"}}</button> data-url="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
data-name="{{.Name}}">{{$.i18n.Tr "org.teams.leave"}}</button>
</form> </form>
{{else if $.IsOrganizationOwner}} {{else if $.IsOrganizationOwner}}
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/join"> <form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/join">
@ -44,4 +45,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui small basic delete modal" id="leave-team">
<div class="ui icon header">
{{svg "octicon-x" 16 "close inside"}}
{{$.i18n.Tr "org.teams.leave"}}
</div>
<div class="content">
<p>{{$.i18n.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@ -72,7 +72,7 @@
<div class="item"> <div class="item">
{{if not .IsPrimary}} {{if not .IsPrimary}}
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="delete-email" data-url="{{AppSubUrl}}/user/settings/account/email/delete" data-id="{{.ID}}"> <button class="ui red tiny button delete-button" data-modal-id="delete-email" data-url="{{AppSubUrl}}/user/settings/account/email/delete" data-id="{{.ID}}">
{{$.i18n.Tr "settings.delete_email"}} {{$.i18n.Tr "settings.delete_email"}}
</button> </button>
</div> </div>
@ -185,7 +185,7 @@
<input id="password-confirmation" name="password" type="password" autocomplete="off" required> <input id="password-confirmation" name="password" type="password" autocomplete="off" required>
</div> </div>
<div class="field"> <div class="field">
<div class="ui red button delete-button" id="delete-account" data-type="form" data-form="#delete-form"> <div class="ui red button delete-button" data-modal-id="delete-account" data-type="form" data-form="#delete-form">
{{.i18n.Tr "settings.confirm_delete_account"}} {{.i18n.Tr "settings.confirm_delete_account"}}
</div> </div>
<a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.i18n.Tr "auth.forgot_password"}}</a> <a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.i18n.Tr "auth.forgot_password"}}</a>

View File

@ -14,7 +14,7 @@
{{range .Tokens}} {{range .Tokens}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="delete-token" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> <button class="ui red tiny button delete-button" data-modal-id="delete-token" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
{{svg "octicon-trash" 16 "mr-2"}} {{svg "octicon-trash" 16 "mr-2"}}
{{$.i18n.Tr "settings.delete_token"}} {{$.i18n.Tr "settings.delete_token"}}
</button> </button>

View File

@ -13,7 +13,7 @@
{{svg "octicon-pencil" 16 "mr-2"}} {{svg "octicon-pencil" 16 "mr-2"}}
{{$.i18n.Tr "settings.oauth2_application_edit"}} {{$.i18n.Tr "settings.oauth2_application_edit"}}
</a> </a>
<button class="ui red tiny button delete-button" id="remove-gitea-oauth2-application" <button class="ui red tiny button delete-button" data-modal-id="remove-gitea-oauth2-application"
data-url="{{AppSubUrl}}/user/settings/applications/oauth2/delete" data-url="{{AppSubUrl}}/user/settings/applications/oauth2/delete"
data-id="{{$app.ID}}"> data-id="{{$app.ID}}">
{{svg "octicon-trash" 16 "mr-2"}} {{svg "octicon-trash" 16 "mr-2"}}

View File

@ -9,7 +9,7 @@
{{range $grant := .Grants}} {{range $grant := .Grants}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="revoke-gitea-oauth2-grant" <button class="ui red tiny button delete-button" data-modal-id="revoke-gitea-oauth2-grant"
data-url="{{AppSubUrl}}/user/settings/applications/oauth2/revoke" data-url="{{AppSubUrl}}/user/settings/applications/oauth2/revoke"
data-id="{{$grant.ID}}"> data-id="{{$grant.ID}}">
{{$.i18n.Tr "settings.revoke_key"}} {{$.i18n.Tr "settings.revoke_key"}}

View File

@ -12,7 +12,7 @@
{{range .GPGKeys}} {{range .GPGKeys}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="delete-gpg" data-url="{{$.Link}}/delete?type=gpg" data-id="{{.ID}}"> <button class="ui red tiny button delete-button" data-modal-id="delete-gpg" data-url="{{$.Link}}/delete?type=gpg" data-id="{{.ID}}">
{{$.i18n.Tr "settings.delete_key"}} {{$.i18n.Tr "settings.delete_key"}}
</button> </button>
{{if and (not .Verified) (ne $.VerifyingID .KeyID)}} {{if and (not .Verified) (ne $.VerifyingID .KeyID)}}

View File

@ -17,7 +17,7 @@
{{range .Principals}} {{range .Principals}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="delete-principal" data-url="{{$.Link}}/delete?type=principal" data-id="{{.ID}}"> <button class="ui red tiny button delete-button" data-modal-id="delete-principal" data-url="{{$.Link}}/delete?type=principal" data-id="{{.ID}}">
{{$.i18n.Tr "settings.delete_key"}} {{$.i18n.Tr "settings.delete_key"}}
</button> </button>
</div> </div>

View File

@ -16,7 +16,7 @@
{{range $index, $key := .Keys}} {{range $index, $key := .Keys}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button{{if index $.ExternalKeys $index}} disabled{{end}}" id="delete-ssh" data-url="{{$.Link}}/delete?type=ssh" data-id="{{.ID}}"{{if index $.ExternalKeys $index}} title="{{$.i18n.Tr "settings.ssh_externally_managed"}}"{{end}}> <button class="ui red tiny button delete-button{{if index $.ExternalKeys $index}} disabled{{end}}" data-modal-id="delete-ssh" data-url="{{$.Link}}/delete?type=ssh" data-id="{{.ID}}"{{if index $.ExternalKeys $index}} title="{{$.i18n.Tr "settings.ssh_externally_managed"}}"{{end}}>
{{$.i18n.Tr "settings.delete_key"}} {{$.i18n.Tr "settings.delete_key"}}
</button> </button>
</div> </div>

View File

@ -10,7 +10,7 @@
{{range $loginSource, $provider := .AccountLinks}} {{range $loginSource, $provider := .AccountLinks}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="delete-account-link" data-url="{{AppSubUrl}}/user/settings/security/account_link" data-id="{{$loginSource.ID}}"> <button class="ui red tiny button delete-button" data-modal-id="delete-account-link" data-url="{{AppSubUrl}}/user/settings/security/account_link" data-id="{{$loginSource.ID}}">
{{$.i18n.Tr "settings.delete_key"}} {{$.i18n.Tr "settings.delete_key"}}
</button> </button>
</div> </div>

View File

@ -9,7 +9,7 @@
{{range .OpenIDs}} {{range .OpenIDs}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="delete-openid" data-url="{{AppSubUrl}}/user/settings/security/openid/delete" data-id="{{.ID}}"> <button class="ui red tiny button delete-button" data-modal-id="delete-openid" data-url="{{AppSubUrl}}/user/settings/security/openid/delete" data-id="{{.ID}}">
{{$.i18n.Tr "settings.delete_key"}} {{$.i18n.Tr "settings.delete_key"}}
</button> </button>
</div> </div>

View File

@ -13,7 +13,7 @@
<form class="ui form" action="{{AppSubUrl}}/user/settings/security/two_factor/disable" method="post" enctype="multipart/form-data" id="disable-form"> <form class="ui form" action="{{AppSubUrl}}/user/settings/security/two_factor/disable" method="post" enctype="multipart/form-data" id="disable-form">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<p>{{.i18n.Tr "settings.twofa_disable_note"}}</p> <p>{{.i18n.Tr "settings.twofa_disable_note"}}</p>
<div class="ui red button delete-button" id="disable-twofa" data-type="form" data-form="#disable-form">{{$.i18n.Tr "settings.twofa_disable"}}</div> <div class="ui red button delete-button" data-modal-id="disable-twofa" data-type="form" data-form="#disable-form">{{$.i18n.Tr "settings.twofa_disable"}}</div>
</form> </form>
{{else}} {{else}}
<p>{{.i18n.Tr "settings.twofa_not_enrolled"}}</p> <p>{{.i18n.Tr "settings.twofa_not_enrolled"}}</p>

View File

@ -8,7 +8,7 @@
{{range .U2FRegistrations}} {{range .U2FRegistrations}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<button class="ui red tiny button delete-button" id="delete-registration" data-url="{{$.Link}}/u2f/delete" data-id="{{.ID}}"> <button class="ui red tiny button delete-button" data-modal-id="delete-registration" data-url="{{$.Link}}/u2f/delete" data-id="{{.ID}}">
{{$.i18n.Tr "settings.delete_key"}} {{$.i18n.Tr "settings.delete_key"}}
</button> </button>
</div> </div>

View File

@ -2956,13 +2956,19 @@ $(() => {
function showDeletePopup() { function showDeletePopup() {
const $this = $(this); const $this = $(this);
const dataArray = $this.data();
let filter = ''; let filter = '';
if ($this.attr('id')) { if ($this.data('modal-id')) {
filter += `#${$this.attr('id')}`; filter += `#${$this.data('modal-id')}`;
} }
const dialog = $(`.delete.modal${filter}`); const dialog = $(`.delete.modal${filter}`);
dialog.find('.name').text($this.data('name')); dialog.find('.name').text($this.data('name'));
for (const [key, value] of Object.entries(dataArray)) {
if (key && key.startsWith('data')) {
dialog.find(`.${key}`).text(value);
}
}
dialog.modal({ dialog.modal({
closable: false, closable: false,
@ -2972,10 +2978,19 @@ function showDeletePopup() {
return; return;
} }
$.post($this.data('url'), { const postData = {
_csrf: csrf, _csrf: csrf,
id: $this.data('id') };
}).done((data) => { for (const [key, value] of Object.entries(dataArray)) {
if (key && key.startsWith('data')) {
postData[key.substr(4)] = value;
}
if (key === 'id') {
postData['id'] = value;
}
}
$.post($this.data('url'), postData).done((data) => {
window.location.href = data.redirect; window.location.href = data.redirect;
}); });
} }