@@ -8,7 +8,7 @@ import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
88import { svg } from '../svg.js' ;
99import { hideElem , showElem , toggleElem } from '../utils/dom.js' ;
1010import { htmlEscape } from 'escape-goat' ;
11- import { createTippy , showTemporaryTooltip } from '../modules/tippy.js' ;
11+ import { showTemporaryTooltip } from '../modules/tippy.js' ;
1212import { confirmModal } from './comp/ConfirmModal.js' ;
1313import { showErrorToast } from '../modules/toast.js' ;
1414
@@ -64,9 +64,9 @@ export function initGlobalButtonClickOnEnter() {
6464 } ) ;
6565}
6666
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"
6868// more details are in the backend's fetch-redirect handler
69- function doRedirect ( redirect ) {
69+ function fetchActionDoRedirect ( redirect ) {
7070 const form = document . createElement ( 'form' ) ;
7171 const input = document . createElement ( 'input' ) ;
7272 form . method = 'post' ;
@@ -79,6 +79,33 @@ function doRedirect(redirect) {
7979 form . submit ( ) ;
8080}
8181
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+
82109async function formFetchAction ( e ) {
83110 if ( ! e . target . classList . contains ( 'form-fetch-action' ) ) return ;
84111
@@ -115,50 +142,7 @@ async function formFetchAction(e) {
115142 reqOpt . body = formData ;
116143 }
117144
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 ) ;
162146}
163147
164148export function initGlobalCommon ( ) {
@@ -209,6 +193,7 @@ export function initGlobalCommon() {
209193 $ ( '.tabular.menu .item' ) . tab ( ) ;
210194
211195 document . addEventListener ( 'submit' , formFetchAction ) ;
196+ document . addEventListener ( 'click' , linkAction ) ;
212197}
213198
214199export function initGlobalDropzone ( ) {
@@ -269,41 +254,29 @@ export function initGlobalDropzone() {
269254}
270255
271256async function linkAction ( e ) {
272- e . preventDefault ( ) ;
273-
274257 // A "link-action" can post AJAX request to its "data-url"
275258 // Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading.
276259 // 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 ;
277262
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 ;
296269 } ;
297270
298- const modalConfirmContent = htmlEscape ( $this . attr ( 'data-modal-confirm' ) || '' ) ;
271+ const modalConfirmContent = htmlEscape ( el . getAttribute ( 'data-modal-confirm' ) || '' ) ;
299272 if ( ! modalConfirmContent ) {
300- doRequest ( ) ;
273+ await doRequest ( ) ;
301274 return ;
302275 }
303276
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' ) ;
305278 if ( await confirmModal ( { content : modalConfirmContent , buttonColor : isRisky ? 'orange' : 'green' } ) ) {
306- doRequest ( ) ;
279+ await doRequest ( ) ;
307280 }
308281}
309282
@@ -354,7 +327,6 @@ export function initGlobalLinkActions() {
354327
355328 // Helpers.
356329 $ ( '.delete-button' ) . on ( 'click' , showDeletePopup ) ;
357- $ ( '.link-action' ) . on ( 'click' , linkAction ) ;
358330}
359331
360332function initGlobalShowModal ( ) {
0 commit comments