@@ -8,7 +8,7 @@ import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
8
8
import { svg } from '../svg.js' ;
9
9
import { hideElem , showElem , toggleElem } from '../utils/dom.js' ;
10
10
import { htmlEscape } from 'escape-goat' ;
11
- import { createTippy , showTemporaryTooltip } from '../modules/tippy.js' ;
11
+ import { showTemporaryTooltip } from '../modules/tippy.js' ;
12
12
import { confirmModal } from './comp/ConfirmModal.js' ;
13
13
import { showErrorToast } from '../modules/toast.js' ;
14
14
@@ -64,9 +64,9 @@ export function initGlobalButtonClickOnEnter() {
64
64
} ) ;
65
65
}
66
66
67
- // doRedirect does real redirection to bypass the browser's limitations of "location"
67
+ // fetchActionDoRedirect does real redirection to bypass the browser's limitations of "location"
68
68
// more details are in the backend's fetch-redirect handler
69
- function doRedirect ( redirect ) {
69
+ function fetchActionDoRedirect ( redirect ) {
70
70
const form = document . createElement ( 'form' ) ;
71
71
const input = document . createElement ( 'input' ) ;
72
72
form . method = 'post' ;
@@ -79,6 +79,33 @@ function doRedirect(redirect) {
79
79
form . submit ( ) ;
80
80
}
81
81
82
+ async function fetchActionDoRequest ( actionElem , url , opt ) {
83
+ try {
84
+ const resp = await fetch ( url , opt ) ;
85
+ if ( resp . status === 200 ) {
86
+ let { redirect} = await resp . json ( ) ;
87
+ redirect = redirect || actionElem . getAttribute ( 'data-redirect' ) ;
88
+ actionElem . classList . remove ( 'dirty' ) ; // remove the areYouSure check before reloading
89
+ if ( redirect ) {
90
+ fetchActionDoRedirect ( redirect ) ;
91
+ } else {
92
+ window . location . reload ( ) ;
93
+ }
94
+ } else if ( resp . status >= 400 && resp . status < 500 ) {
95
+ const data = await resp . json ( ) ;
96
+ // the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
97
+ // but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
98
+ await showErrorToast ( data . errorMessage || `server error: ${ resp . status } ` ) ;
99
+ } else {
100
+ await showErrorToast ( `server error: ${ resp . status } ` ) ;
101
+ }
102
+ } catch ( e ) {
103
+ console . error ( 'error when doRequest' , e ) ;
104
+ actionElem . classList . remove ( 'is-loading' , 'small-loading-icon' ) ;
105
+ await showErrorToast ( i18n . network_error ) ;
106
+ }
107
+ }
108
+
82
109
async function formFetchAction ( e ) {
83
110
if ( ! e . target . classList . contains ( 'form-fetch-action' ) ) return ;
84
111
@@ -115,50 +142,7 @@ async function formFetchAction(e) {
115
142
reqOpt . body = formData ;
116
143
}
117
144
118
- let errorTippy ;
119
- const onError = ( msg ) => {
120
- formEl . classList . remove ( 'is-loading' , 'small-loading-icon' ) ;
121
- if ( errorTippy ) errorTippy . destroy ( ) ;
122
- // TODO: use a better toast UI instead of the tippy. If the form height is large, the tippy position is not good
123
- errorTippy = createTippy ( formEl , {
124
- content : msg ,
125
- interactive : true ,
126
- showOnCreate : true ,
127
- hideOnClick : true ,
128
- role : 'alert' ,
129
- theme : 'form-fetch-error' ,
130
- trigger : 'manual' ,
131
- arrow : false ,
132
- } ) ;
133
- } ;
134
-
135
- const doRequest = async ( ) => {
136
- try {
137
- const resp = await fetch ( reqUrl , reqOpt ) ;
138
- if ( resp . status === 200 ) {
139
- const { redirect} = await resp . json ( ) ;
140
- formEl . classList . remove ( 'dirty' ) ; // remove the areYouSure check before reloading
141
- if ( redirect ) {
142
- doRedirect ( redirect ) ;
143
- } else {
144
- window . location . reload ( ) ;
145
- }
146
- } else if ( resp . status >= 400 && resp . status < 500 ) {
147
- const data = await resp . json ( ) ;
148
- // the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
149
- // but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
150
- onError ( data . errorMessage || `server error: ${ resp . status } ` ) ;
151
- } else {
152
- onError ( `server error: ${ resp . status } ` ) ;
153
- }
154
- } catch ( e ) {
155
- console . error ( 'error when doRequest' , e ) ;
156
- onError ( i18n . network_error ) ;
157
- }
158
- } ;
159
-
160
- // TODO: add "confirm" support like "link-action" in the future
161
- await doRequest ( ) ;
145
+ await fetchActionDoRequest ( formEl , reqUrl , reqOpt ) ;
162
146
}
163
147
164
148
export function initGlobalCommon ( ) {
@@ -209,6 +193,7 @@ export function initGlobalCommon() {
209
193
$ ( '.tabular.menu .item' ) . tab ( ) ;
210
194
211
195
document . addEventListener ( 'submit' , formFetchAction ) ;
196
+ document . addEventListener ( 'click' , linkAction ) ;
212
197
}
213
198
214
199
export function initGlobalDropzone ( ) {
@@ -269,41 +254,29 @@ export function initGlobalDropzone() {
269
254
}
270
255
271
256
async function linkAction ( e ) {
272
- e . preventDefault ( ) ;
273
-
274
257
// A "link-action" can post AJAX request to its "data-url"
275
258
// Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading.
276
259
// If the "link-action" has "data-modal-confirm" attribute, a confirm modal dialog will be shown before taking action.
260
+ const el = e . target . closest ( '.link-action' ) ;
261
+ if ( ! el ) return ;
277
262
278
- const $this = $ ( this ) ;
279
- const redirect = $this . attr ( 'data-redirect' ) ;
280
-
281
- const doRequest = ( ) => {
282
- $this . prop ( 'disabled' , true ) ;
283
- $ . post ( $this . attr ( 'data-url' ) , {
284
- _csrf : csrfToken
285
- } ) . done ( ( data ) => {
286
- if ( data && data . redirect ) {
287
- window . location . href = data . redirect ;
288
- } else if ( redirect ) {
289
- window . location . href = redirect ;
290
- } else {
291
- window . location . reload ( ) ;
292
- }
293
- } ) . always ( ( ) => {
294
- $this . prop ( 'disabled' , false ) ;
295
- } ) ;
263
+ e . preventDefault ( ) ;
264
+ const url = el . getAttribute ( 'data-url' ) ;
265
+ const doRequest = async ( ) => {
266
+ el . disabled = true ;
267
+ await fetchActionDoRequest ( el , url , { method : 'POST' , headers : { 'X-Csrf-Token' : csrfToken } } ) ;
268
+ el . disabled = false ;
296
269
} ;
297
270
298
- const modalConfirmContent = htmlEscape ( $this . attr ( 'data-modal-confirm' ) || '' ) ;
271
+ const modalConfirmContent = htmlEscape ( el . getAttribute ( 'data-modal-confirm' ) || '' ) ;
299
272
if ( ! modalConfirmContent ) {
300
- doRequest ( ) ;
273
+ await doRequest ( ) ;
301
274
return ;
302
275
}
303
276
304
- const isRisky = $this . hasClass ( 'red' ) || $this . hasClass ( 'yellow' ) || $this . hasClass ( 'orange' ) || $this . hasClass ( 'negative' ) ;
277
+ const isRisky = el . classList . contains ( 'red' ) || el . classList . contains ( 'yellow' ) || el . classList . contains ( 'orange' ) || el . classList . contains ( 'negative' ) ;
305
278
if ( await confirmModal ( { content : modalConfirmContent , buttonColor : isRisky ? 'orange' : 'green' } ) ) {
306
- doRequest ( ) ;
279
+ await doRequest ( ) ;
307
280
}
308
281
}
309
282
@@ -354,7 +327,6 @@ export function initGlobalLinkActions() {
354
327
355
328
// Helpers.
356
329
$ ( '.delete-button' ) . on ( 'click' , showDeletePopup ) ;
357
- $ ( '.link-action' ) . on ( 'click' , linkAction ) ;
358
330
}
359
331
360
332
function initGlobalShowModal ( ) {
0 commit comments