Skip to content

Commit 6bbccdd

Browse files
Improve AJAX link and modal confirm dialog (#25210)
Clarify the "link-action" behavior: > // A "link-action" can post AJAX request to its "data-url" > // Then the browser is redirect to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading. And enhance the "link-action" to support showing a modal dialog for confirm. A similar general approach could also help PRs like #22344 (comment) > // If the "link-action" has "data-modal-confirm(-html)" attribute, a confirm modal dialog will be shown before taking action. And a lot of duplicate code can be removed now. A good framework design can help to avoid code copying&pasting. --------- Co-authored-by: silverwind <[email protected]>
1 parent a51b115 commit 6bbccdd

File tree

11 files changed

+86
-157
lines changed

11 files changed

+86
-157
lines changed

templates/admin/user/edit.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186

187187
<div class="field">
188188
<button class="ui green button">{{$.locale.Tr "settings.update_avatar"}}</button>
189-
<a class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</a>{{/* TODO: Convert links without href to buttons for a11y */}}
189+
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete" data-redirect="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</button>
190190
</div>
191191
</form>
192192
</div>

templates/base/head_script.tmpl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
3232
mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
3333
{{/* this global i18n object should only contain general texts. for specialized texts, it should be provided inside the related modules by: (1) API response (2) HTML data-attribute (3) PageData */}}
3434
i18n: {
35-
copy_success: '{{.locale.Tr "copy_success"}}',
36-
copy_error: '{{.locale.Tr "copy_error"}}',
37-
error_occurred: '{{.locale.Tr "error.occurred"}}',
38-
network_error: '{{.locale.Tr "error.network_error"}}',
39-
remove_label_str: '{{.locale.Tr "remove_label_str"}}',
35+
copy_success: {{.locale.Tr "copy_success"}},
36+
copy_error: {{.locale.Tr "copy_error"}},
37+
error_occurred: {{.locale.Tr "error.occurred"}},
38+
network_error: {{.locale.Tr "error.network_error"}},
39+
remove_label_str: {{.locale.Tr "remove_label_str"}},
40+
modal_confirm: {{.locale.Tr "modal.confirm"}},
41+
modal_cancel: {{.locale.Tr "modal.cancel"}},
4042
},
4143
};
4244
{{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}

templates/org/settings/options.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090

9191
<div class="field">
9292
<button class="ui green button">{{$.locale.Tr "settings.update_avatar"}}</button>
93-
<a class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</a>
93+
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete" data-redirect="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</button>
9494
</div>
9595
</form>
9696
</div>

templates/org/team/members.tmpl

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
{{template "org/team/navbar" .}}
1010
{{if .IsOrganizationOwner}}
1111
<div class="ui attached segment">
12-
<form class="ui form ignore-dirty" id="add-member-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post">
12+
<form class="ui form ignore-dirty gt-df gt-fw gt-gap-3" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post">
1313
{{.CsrfTokenHtml}}
1414
<input type="hidden" name="uid" value="{{.SignedUser.ID}}">
15-
<div class="inline field ui left">
16-
<div id="search-user-box" class="ui search"{{if .IsEmailInviteEnabled}} data-allow-email="true" data-allow-email-description="{{.locale.Tr "org.teams.invite_team_member" $.Team.Name}}"{{end}}>
17-
<div class="ui input">
18-
<input class="prompt" name="uname" placeholder="{{.locale.Tr "repo.settings.search_user_placeholder"}}" autocomplete="off" required>
19-
</div>
15+
<div id="search-user-box" class="ui search gt-mr-3"{{if .IsEmailInviteEnabled}} data-allow-email="true" data-allow-email-description="{{.locale.Tr "org.teams.invite_team_member" $.Team.Name}}"{{end}}>
16+
<div class="ui input">
17+
<input class="prompt" name="uname" placeholder="{{.locale.Tr "repo.settings.search_user_placeholder"}}" autocomplete="off" required>
2018
</div>
2119
</div>
2220
<button class="ui green button">{{.locale.Tr "org.teams.add_team_member"}}</button>

templates/org/team/repositories.tmpl

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,19 @@
99
{{template "org/team/navbar" .}}
1010
{{$canAddRemove := and $.IsOrganizationOwner (not $.Team.IncludesAllRepositories)}}
1111
{{if $canAddRemove}}
12-
<div class="ui attached segment" id="repo-top-segment">
13-
<div class="inline ui field left">
14-
<form class="ui form ignore-dirty" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/add" method="post">
15-
{{.CsrfTokenHtml}}
16-
<div class="inline field ui left">
17-
<div id="search-repo-box" data-uid="{{.Org.ID}}" class="ui search">
18-
<div class="ui input">
19-
<input class="prompt" name="repo_name" placeholder="{{.locale.Tr "org.teams.search_repo_placeholder"}}" autocomplete="off" required>
20-
</div>
21-
</div>
12+
<div class="ui attached segment gt-df gt-fw gt-gap-3">
13+
<form class="ui form ignore-dirty gt-f1 gt-dif" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/add" method="post">
14+
{{.CsrfTokenHtml}}
15+
<div id="search-repo-box" data-uid="{{.Org.ID}}" class="ui search">
16+
<div class="ui input">
17+
<input class="prompt" name="repo_name" placeholder="{{.locale.Tr "org.teams.search_repo_placeholder"}}" autocomplete="off" required>
2218
</div>
23-
<button class="ui green button">{{.locale.Tr "add"}}</button>
24-
</form>
25-
</div>
26-
<div class="inline ui field right">
27-
<form class="ui form" id="repo-multiple-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/repositories" method="post">
28-
<button class="ui green button add-all-button" data-modal-id="org-team-add-all-repo" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/addall">{{.locale.Tr "add_all"}}</button>
29-
<button class="ui red button delete-button" data-modal-id="org-team-remove-all-repo" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/removeall">{{.locale.Tr "remove_all"}}</button>
30-
</form>
19+
</div>
20+
<button class="ui green button gt-ml-3">{{.locale.Tr "add"}}</button>
21+
</form>
22+
<div class="gt-dib">
23+
<button class="ui green button link-action" data-modal-confirm="{{.locale.Tr "org.teams.add_all_repos_desc"}}" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/addall">{{.locale.Tr "add_all"}}</button>
24+
<button class="ui red button link-action" data-modal-confirm="{{.locale.Tr "org.teams.remove_all_repos_desc"}}" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/removeall">{{.locale.Tr "remove_all"}}</button>
3125
</div>
3226
</div>
3327
{{end}}
@@ -64,26 +58,4 @@
6458
</div>
6559
</div>
6660

67-
<div class="ui g-modal-confirm delete modal" id="org-team-remove-all-repo">
68-
<div class="header">
69-
{{svg "octicon-trash"}}
70-
{{.locale.Tr "org.teams.remove_all_repos_title"}}
71-
</div>
72-
<div class="content">
73-
<p>{{.locale.Tr "org.teams.remove_all_repos_desc"}}</p>
74-
</div>
75-
{{template "base/modal_actions_confirm" .}}
76-
</div>
77-
78-
<div class="ui g-modal-confirm addall modal" id="org-team-add-all-repo">
79-
<div class="header">
80-
{{svg "octicon-globe"}}
81-
{{.locale.Tr "org.teams.add_all_repos_title"}}
82-
</div>
83-
<div class="content">
84-
<p>{{.locale.Tr "org.teams.add_all_repos_desc"}}</p>
85-
</div>
86-
{{template "base/modal_actions_confirm" .}}
87-
</div>
88-
8961
{{template "base/footer" .}}

templates/repo/branch/list.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
{{end}}
166166
{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
167167
{{if .IsDeleted}}
168-
<button class="btn interact-bg gt-p-3 undo-button" data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{.DeletedBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.Name)}}">
168+
<button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{.DeletedBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.Name)}}">
169169
<span class="text blue">
170170
{{svg "octicon-reply"}}
171171
</span>

templates/repo/settings/options.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161

6262
<div class="field">
6363
<button class="ui green button">{{$.locale.Tr "settings.update_avatar"}}</button>
64-
<a class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</a>
64+
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete" data-redirect="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</button>
6565
</div>
6666
</form>
6767

templates/user/settings/profile.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125

126126
<div class="field">
127127
<button class="ui green button">{{$.locale.Tr "settings.update_avatar"}}</button>
128-
<button class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</button>
128+
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete" data-redirect="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</button>
129129
</div>
130130
</form>
131131
</div>

tests/integration/branches_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestDeleteBranch(t *testing.T) {
3434
func TestUndoDeleteBranch(t *testing.T) {
3535
onGiteaRun(t, func(t *testing.T, u *url.URL) {
3636
deleteBranch(t)
37-
htmlDoc, name := branchAction(t, ".undo-button")
37+
htmlDoc, name := branchAction(t, ".restore-branch-button")
3838
assert.Contains(t,
3939
htmlDoc.doc.Find(".ui.positive.message").Text(),
4040
translation.NewLocale("en-US").Tr("repo.branch.restore_success", name),

web_src/css/org.css

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -205,29 +205,6 @@
205205
margin: 0;
206206
}
207207

208-
.organization.teams #add-repo-form input,
209-
.organization.teams #repo-multiple-form input,
210-
.organization.teams #add-member-form input {
211-
margin-left: 0;
212-
}
213-
214-
.organization.teams #add-repo-form .ui.button,
215-
.organization.teams #repo-multiple-form .ui.button,
216-
.organization.teams #add-member-form .ui.button {
217-
margin-left: 5px;
218-
margin-top: -3px;
219-
}
220-
221-
.organization.teams #repo-top-segment {
222-
height: 60px;
223-
}
224-
225-
@media (max-width: 767.98px) {
226-
.organization.teams #repo-top-segment {
227-
height: 100px;
228-
}
229-
}
230-
231208
.org-team-navbar .active.item {
232209
background: var(--color-box-body) !important;
233210
}

web_src/js/features/common-global.js

Lines changed: 57 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {svg} from '../svg.js';
88
import {hideElem, showElem, toggleElem} from '../utils/dom.js';
99
import {htmlEscape} from 'escape-goat';
1010

11-
const {appUrl, csrfToken} = window.config;
11+
const {appUrl, csrfToken, i18n} = window.config;
1212

1313
export function initGlobalFormDirtyLeaveConfirm() {
1414
// Warn users that try to leave a page after entering data into a form.
@@ -172,6 +172,62 @@ export function initGlobalDropzone() {
172172
}
173173
}
174174

175+
function linkAction(e) {
176+
e.preventDefault();
177+
178+
// A "link-action" can post AJAX request to its "data-url"
179+
// Then the browser is redirect to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading.
180+
// If the "link-action" has "data-modal-confirm(-html)" attribute, a confirm modal dialog will be shown before taking action.
181+
182+
const $this = $(e.target);
183+
const redirect = $this.attr('data-redirect');
184+
185+
const request = () => {
186+
$this.prop('disabled', true);
187+
$.post($this.attr('data-url'), {
188+
_csrf: csrfToken
189+
}).done((data) => {
190+
if (data && data.redirect) {
191+
window.location.href = data.redirect;
192+
} else if (redirect) {
193+
window.location.href = redirect;
194+
} else {
195+
window.location.reload();
196+
}
197+
}).always(() => {
198+
$this.prop('disabled', false);
199+
});
200+
};
201+
202+
const modalConfirmHtml = htmlEscape($this.attr('data-modal-confirm') || '');
203+
if (!modalConfirmHtml) {
204+
request();
205+
return;
206+
}
207+
208+
const okButtonColor = $this.hasClass('red') || $this.hasClass('yellow') || $this.hasClass('orange') || $this.hasClass('negative') ? 'orange' : 'green';
209+
210+
const $modal = $(`
211+
<div class="ui g-modal-confirm modal">
212+
<div class="content">${modalConfirmHtml}</div>
213+
<div class="actions">
214+
<button class="ui basic cancel button">${svg('octicon-x')} ${i18n.modal_cancel}</button>
215+
<button class="ui ${okButtonColor} ok button">${svg('octicon-check')} ${i18n.modal_confirm}</button>
216+
</div>
217+
</div>
218+
`);
219+
220+
$modal.appendTo(document.body);
221+
$modal.modal({
222+
onApprove() {
223+
request();
224+
},
225+
onHidden() {
226+
$modal.remove();
227+
},
228+
}).modal('show');
229+
}
230+
175231
export function initGlobalLinkActions() {
176232
function showDeletePopup(e) {
177233
e.preventDefault();
@@ -217,75 +273,9 @@ export function initGlobalLinkActions() {
217273
}).modal('show');
218274
}
219275

220-
function showAddAllPopup(e) {
221-
e.preventDefault();
222-
const $this = $(this);
223-
let filter = '';
224-
if ($this.attr('data-modal-id')) {
225-
filter += `#${$this.attr('data-modal-id')}`;
226-
}
227-
228-
const dialog = $(`.addall.modal${filter}`);
229-
dialog.find('.name').text($this.data('name'));
230-
231-
dialog.modal({
232-
closable: false,
233-
onApprove() {
234-
if ($this.data('type') === 'form') {
235-
$($this.data('form')).trigger('submit');
236-
return;
237-
}
238-
239-
$.post($this.data('url'), {
240-
_csrf: csrfToken,
241-
id: $this.data('id')
242-
}).done((data) => {
243-
window.location.href = data.redirect;
244-
});
245-
}
246-
}).modal('show');
247-
}
248-
249-
function linkAction(e) {
250-
e.preventDefault();
251-
const $this = $(this);
252-
const redirect = $this.data('redirect');
253-
$this.prop('disabled', true);
254-
$.post($this.data('url'), {
255-
_csrf: csrfToken
256-
}).done((data) => {
257-
if (data.redirect) {
258-
window.location.href = data.redirect;
259-
} else if (redirect) {
260-
window.location.href = redirect;
261-
} else {
262-
window.location.reload();
263-
}
264-
}).always(() => {
265-
$this.prop('disabled', false);
266-
});
267-
}
268-
269276
// Helpers.
270277
$('.delete-button').on('click', showDeletePopup);
271278
$('.link-action').on('click', linkAction);
272-
273-
// FIXME: this function is only used once, and not common, not well designed. should be refactored later
274-
$('.add-all-button').on('click', showAddAllPopup);
275-
276-
// FIXME: this is only used once, and should be replace with `link-action` instead
277-
$('.undo-button').on('click', function () {
278-
const $this = $(this);
279-
$this.prop('disabled', true);
280-
$.post($this.data('url'), {
281-
_csrf: csrfToken,
282-
id: $this.data('id')
283-
}).done((data) => {
284-
window.location.href = data.redirect;
285-
}).always(() => {
286-
$this.prop('disabled', false);
287-
});
288-
});
289279
}
290280

291281
export function initGlobalButtons() {
@@ -346,16 +336,6 @@ export function initGlobalButtons() {
346336
initCompColorPicker();
347337
}
348338
});
349-
350-
$('.delete-post.button').on('click', function (e) {
351-
e.preventDefault();
352-
const $this = $(this);
353-
$.post($this.attr('data-request-url'), {
354-
_csrf: csrfToken
355-
}).done(() => {
356-
window.location.href = $this.attr('data-done-url');
357-
});
358-
});
359339
}
360340

361341
/**

0 commit comments

Comments
 (0)