@@ -5,130 +5,182 @@ require('./preview_markdown');
5
5
6
6
window . DropzoneInput = ( function ( ) {
7
7
function DropzoneInput ( form ) {
8
- var $ mdArea, alertAttr , alertClass , appendToTextArea , btnAlert , child , closeAlertMessage , closeSpinner , divAlert , divHover , divSpinner , dropzone , form_dropzone , form_textarea , getFilename , handlePaste , iconPaperclip , iconSpinner , insertToTextArea , isImage , max_file_size , pasteText , uploads_path , showError , showSpinner , uploadFile , uploadProgress ;
8
+ var updateAttachingMessage , $attachingFileMessage , $ mdArea, $attachButton , $cancelButton , $retryLink , $uploadingErrorContainer , $uploadingErrorMessage , $uploadProgress , $uploadingProgressContainer , appendToTextArea , btnAlert , child , closeAlertMessage , closeSpinner , divHover , divSpinner , dropzone , $formDropzone , formTextarea , getFilename , handlePaste , iconPaperclip , iconSpinner , insertToTextArea , isImage , maxFileSize , pasteText , uploadsPath , showError , showSpinner , uploadFile ;
9
9
Dropzone . autoDiscover = false ;
10
- alertClass = "alert alert-danger alert-dismissable div-dropzone-alert" ;
11
- alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"" ;
12
- divHover = "<div class=\"div-dropzone-hover\"></div>" ;
13
- divSpinner = "<div class=\"div-dropzone-spinner\"></div>" ;
14
- divAlert = "<div class=\"" + alertClass + "\"></div>" ;
15
- iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>" ;
16
- iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>" ;
17
- uploadProgress = $ ( "<div class=\"div-dropzone-progress\"></div>" ) ;
18
- btnAlert = "<button type=\"button\"" + alertAttr + ">×</button>" ;
19
- uploads_path = window . uploads_path || null ;
20
- max_file_size = gon . max_file_size || 10 ;
21
- form_textarea = $ ( form ) . find ( ".js-gfm-input" ) ;
22
- form_textarea . wrap ( "<div class=\"div-dropzone\"></div>" ) ;
23
- form_textarea . on ( 'paste' , ( function ( _this ) {
10
+ divHover = '<div class="div-dropzone-hover"></div>' ;
11
+ iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>' ;
12
+ $attachButton = form . find ( '.button-attach-file' ) ;
13
+ $attachingFileMessage = form . find ( '.attaching-file-message' ) ;
14
+ $cancelButton = form . find ( '.button-cancel-uploading-files' ) ;
15
+ $retryLink = form . find ( '.retry-uploading-link' ) ;
16
+ $uploadProgress = form . find ( '.uploading-progress' ) ;
17
+ $uploadingErrorContainer = form . find ( '.uploading-error-container' ) ;
18
+ $uploadingErrorMessage = form . find ( '.uploading-error-message' ) ;
19
+ $uploadingProgressContainer = form . find ( '.uploading-progress-container' ) ;
20
+ uploadsPath = window . uploads_path || null ;
21
+ maxFileSize = gon . max_file_size || 10 ;
22
+ formTextarea = form . find ( '.js-gfm-input' ) ;
23
+ formTextarea . wrap ( '<div class="div-dropzone"></div>' ) ;
24
+ formTextarea . on ( 'paste' , ( function ( _this ) {
24
25
return function ( event ) {
25
26
return handlePaste ( event ) ;
26
27
} ;
27
28
} ) ( this ) ) ;
28
- $mdArea = $ ( form_textarea ) . closest ( '.md-area' ) ;
29
- $ ( form ) . setupMarkdownPreview ( ) ;
30
- form_dropzone = $ ( form ) . find ( '.div-dropzone' ) ;
31
- form_dropzone . parent ( ) . addClass ( "div-dropzone-wrapper" ) ;
32
- form_dropzone . append ( divHover ) ;
33
- form_dropzone . find ( ".div-dropzone-hover" ) . append ( iconPaperclip ) ;
34
- form_dropzone . append ( divSpinner ) ;
35
- form_dropzone . find ( ".div-dropzone-spinner" ) . append ( iconSpinner ) ;
36
- form_dropzone . find ( ".div-dropzone-spinner" ) . append ( uploadProgress ) ;
37
- form_dropzone . find ( ".div-dropzone-spinner" ) . css ( {
38
- "opacity" : 0 ,
39
- "display" : "none"
40
- } ) ;
41
29
42
- if ( ! uploads_path ) return ;
30
+ // Add dropzone area to the form.
31
+ $mdArea = formTextarea . closest ( '.md-area' ) ;
32
+ form . setupMarkdownPreview ( ) ;
33
+ $formDropzone = form . find ( '.div-dropzone' ) ;
34
+ $formDropzone . parent ( ) . addClass ( 'div-dropzone-wrapper' ) ;
35
+ $formDropzone . append ( divHover ) ;
36
+ $formDropzone . find ( '.div-dropzone-hover' ) . append ( iconPaperclip ) ;
37
+
38
+ if ( ! uploadsPath ) return ;
43
39
44
- dropzone = form_dropzone . dropzone ( {
45
- url : uploads_path ,
46
- dictDefaultMessage : "" ,
40
+ dropzone = $formDropzone . dropzone ( {
41
+ url : uploadsPath ,
42
+ dictDefaultMessage : '' ,
47
43
clickable : true ,
48
- paramName : " file" ,
49
- maxFilesize : max_file_size ,
44
+ paramName : ' file' ,
45
+ maxFilesize : maxFileSize ,
50
46
uploadMultiple : false ,
51
47
headers : {
52
- " X-CSRF-Token" : $ ( " meta[name=\ "csrf-token\"]" ) . attr ( " content" )
48
+ ' X-CSRF-Token' : $ ( ' meta[name="csrf-token"]' ) . attr ( ' content' )
53
49
} ,
54
50
previewContainer : false ,
55
51
processing : function ( ) {
56
- return $ ( " .div-dropzone-alert" ) . alert ( " close" ) ;
52
+ return $ ( ' .div-dropzone-alert' ) . alert ( ' close' ) ;
57
53
} ,
58
54
dragover : function ( ) {
59
55
$mdArea . addClass ( 'is-dropzone-hover' ) ;
60
- form . find ( " .div-dropzone-hover" ) . css ( " opacity" , 0.7 ) ;
56
+ form . find ( ' .div-dropzone-hover' ) . css ( ' opacity' , 0.7 ) ;
61
57
} ,
62
58
dragleave : function ( ) {
63
59
$mdArea . removeClass ( 'is-dropzone-hover' ) ;
64
- form . find ( " .div-dropzone-hover" ) . css ( " opacity" , 0 ) ;
60
+ form . find ( ' .div-dropzone-hover' ) . css ( ' opacity' , 0 ) ;
65
61
} ,
66
62
drop : function ( ) {
67
63
$mdArea . removeClass ( 'is-dropzone-hover' ) ;
68
- form . find ( " .div-dropzone-hover" ) . css ( " opacity" , 0 ) ;
69
- form_textarea . focus ( ) ;
64
+ form . find ( ' .div-dropzone-hover' ) . css ( ' opacity' , 0 ) ;
65
+ formTextarea . focus ( ) ;
70
66
} ,
71
67
success : function ( header , response ) {
72
68
const processingFileCount = this . getQueuedFiles ( ) . length + this . getUploadingFiles ( ) . length ;
73
69
const shouldPad = processingFileCount >= 1 ;
74
70
75
71
pasteText ( response . link . markdown , shouldPad ) ;
72
+ // Show 'Attach a file' link only when all files have been uploaded.
73
+ if ( ! processingFileCount ) $attachButton . removeClass ( 'hide' ) ;
76
74
} ,
77
- error : function ( temp ) {
78
- var checkIfMsgExists , errorAlert ;
79
- errorAlert = $ ( form ) . find ( '.error-alert' ) ;
80
- checkIfMsgExists = errorAlert . children ( ) . length ;
81
- if ( checkIfMsgExists === 0 ) {
82
- errorAlert . append ( divAlert ) ;
83
- $ ( ".div-dropzone-alert" ) . append ( btnAlert + "Attaching the file failed." ) ;
84
- }
75
+ error : function ( file , errorMessage = 'Attaching the file failed.' , xhr ) {
76
+ // If 'error' event is fired by dropzone, the second parameter is error message.
77
+ // If the 'errorMessage' parameter is empty, the default error message is set.
78
+ // If the 'error' event is fired by backend (xhr) error response, the third parameter is
79
+ // xhr object (xhr.responseText is error message).
80
+ // On error we hide the 'Attach' and 'Cancel' buttons
81
+ // and show an error.
82
+
83
+ // If there's xhr error message, let's show it instead of dropzone's one.
84
+ const message = xhr ? xhr . responseText : errorMessage ;
85
+
86
+ $uploadingErrorContainer . removeClass ( 'hide' ) ;
87
+ $uploadingErrorMessage . html ( message ) ;
88
+ $attachButton . addClass ( 'hide' ) ;
89
+ $cancelButton . addClass ( 'hide' ) ;
85
90
} ,
86
91
totaluploadprogress : function ( totalUploadProgress ) {
87
- uploadProgress . text ( Math . round ( totalUploadProgress ) + "%" ) ;
92
+ updateAttachingMessage ( this . files , $attachingFileMessage ) ;
93
+ $uploadProgress . text ( Math . round ( totalUploadProgress ) + '%' ) ;
94
+ } ,
95
+ sending : function ( file ) {
96
+ // DOM elements already exist.
97
+ // Instead of dynamically generating them,
98
+ // we just either hide or show them.
99
+ $attachButton . addClass ( 'hide' ) ;
100
+ $uploadingErrorContainer . addClass ( 'hide' ) ;
101
+ $uploadingProgressContainer . removeClass ( 'hide' ) ;
102
+ $cancelButton . removeClass ( 'hide' ) ;
88
103
} ,
89
- sending : function ( ) {
90
- form_dropzone . find ( ".div-dropzone-spinner" ) . css ( {
91
- "opacity" : 0.7 ,
92
- "display" : "inherit"
93
- } ) ;
104
+ removedfile : function ( ) {
105
+ $attachButton . removeClass ( 'hide' ) ;
106
+ $cancelButton . addClass ( 'hide' ) ;
107
+ $uploadingProgressContainer . addClass ( 'hide' ) ;
108
+ $uploadingErrorContainer . addClass ( 'hide' ) ;
94
109
} ,
95
110
queuecomplete : function ( ) {
96
- uploadProgress . text ( "" ) ;
97
- $ ( ".dz-preview" ) . remove ( ) ;
98
- $ ( ".markdown-area" ) . trigger ( "input" ) ;
99
- $ ( ".div-dropzone-spinner" ) . css ( {
100
- "opacity" : 0 ,
101
- "display" : "none"
102
- } ) ;
111
+ $ ( '.dz-preview' ) . remove ( ) ;
112
+ $ ( '.markdown-area' ) . trigger ( 'input' ) ;
113
+
114
+ $uploadingProgressContainer . addClass ( 'hide' ) ;
115
+ $cancelButton . addClass ( 'hide' ) ;
103
116
}
104
117
} ) ;
105
- child = $ ( dropzone [ 0 ] ) . children ( "textarea" ) ;
118
+
119
+ child = $ ( dropzone [ 0 ] ) . children ( 'textarea' ) ;
120
+
121
+ // removeAllFiles(true) stops uploading files (if any)
122
+ // and remove them from dropzone files queue.
123
+ $cancelButton . on ( 'click' , ( e ) => {
124
+ const target = e . target . closest ( 'form' ) . querySelector ( '.div-dropzone' ) ;
125
+
126
+ e . preventDefault ( ) ;
127
+ e . stopPropagation ( ) ;
128
+ Dropzone . forElement ( target ) . removeAllFiles ( true ) ;
129
+ } ) ;
130
+
131
+ // If 'error' event is fired, we store a failed files,
132
+ // clear dropzone files queue, change status of failed files to undefined,
133
+ // and add that files to the dropzone files queue again.
134
+ // addFile() adds file to dropzone files queue and upload it.
135
+ $retryLink . on ( 'click' , ( e ) => {
136
+ const dropzoneInstance = Dropzone . forElement ( e . target . closest ( 'form' ) . querySelector ( '.div-dropzone' ) ) ;
137
+ const failedFiles = dropzoneInstance . files ;
138
+
139
+ e . preventDefault ( ) ;
140
+
141
+ // 'true' parameter of removeAllFiles() cancels uploading of files that are being uploaded at the moment.
142
+ dropzoneInstance . removeAllFiles ( true ) ;
143
+
144
+ failedFiles . map ( ( failedFile , i ) => {
145
+ const file = failedFile ;
146
+
147
+ if ( file . status === Dropzone . ERROR ) {
148
+ file . status = undefined ;
149
+ file . accepted = undefined ;
150
+ }
151
+
152
+ return dropzoneInstance . addFile ( file ) ;
153
+ } ) ;
154
+ } ) ;
155
+
106
156
handlePaste = function ( event ) {
107
157
var filename , image , pasteEvent , text ;
108
158
pasteEvent = event . originalEvent ;
109
159
if ( pasteEvent . clipboardData && pasteEvent . clipboardData . items ) {
110
160
image = isImage ( pasteEvent ) ;
111
161
if ( image ) {
112
162
event . preventDefault ( ) ;
113
- filename = getFilename ( pasteEvent ) || " image.png" ;
114
- text = "{{" + filename + "}}" ;
163
+ filename = getFilename ( pasteEvent ) || ' image.png' ;
164
+ text = `{{ ${ filename } }}` ;
115
165
pasteText ( text ) ;
116
166
return uploadFile ( image . getAsFile ( ) , filename ) ;
117
167
}
118
168
}
119
169
} ;
170
+
120
171
isImage = function ( data ) {
121
172
var i , item ;
122
173
i = 0 ;
123
174
while ( i < data . clipboardData . items . length ) {
124
175
item = data . clipboardData . items [ i ] ;
125
- if ( item . type . indexOf ( " image" ) !== - 1 ) {
176
+ if ( item . type . indexOf ( ' image' ) !== - 1 ) {
126
177
return item ;
127
178
}
128
179
i += 1 ;
129
180
}
130
181
return false ;
131
182
} ;
183
+
132
184
pasteText = function ( text , shouldPad ) {
133
185
var afterSelection , beforeSelection , caretEnd , caretStart , textEnd ;
134
186
var formattedText = text ;
@@ -142,31 +194,33 @@ window.DropzoneInput = (function() {
142
194
$ ( child ) . val ( beforeSelection + formattedText + afterSelection ) ;
143
195
textarea . setSelectionRange ( caretStart + formattedText . length , caretEnd + formattedText . length ) ;
144
196
textarea . style . height = `${ textarea . scrollHeight } px` ;
145
- return form_textarea . trigger ( " input" ) ;
197
+ return formTextarea . trigger ( ' input' ) ;
146
198
} ;
199
+
147
200
getFilename = function ( e ) {
148
201
var value ;
149
202
if ( window . clipboardData && window . clipboardData . getData ) {
150
- value = window . clipboardData . getData ( " Text" ) ;
203
+ value = window . clipboardData . getData ( ' Text' ) ;
151
204
} else if ( e . clipboardData && e . clipboardData . getData ) {
152
- value = e . clipboardData . getData ( " text/plain" ) ;
205
+ value = e . clipboardData . getData ( ' text/plain' ) ;
153
206
}
154
207
value = value . split ( "\r" ) ;
155
208
return value . first ( ) ;
156
209
} ;
210
+
157
211
uploadFile = function ( item , filename ) {
158
212
var formData ;
159
213
formData = new FormData ( ) ;
160
- formData . append ( " file" , item , filename ) ;
214
+ formData . append ( ' file' , item , filename ) ;
161
215
return $ . ajax ( {
162
- url : uploads_path ,
163
- type : " POST" ,
216
+ url : uploadsPath ,
217
+ type : ' POST' ,
164
218
data : formData ,
165
- dataType : " json" ,
219
+ dataType : ' json' ,
166
220
processData : false ,
167
221
contentType : false ,
168
222
headers : {
169
- " X-CSRF-Token" : $ ( " meta[name=\ "csrf-token\"]" ) . attr ( " content" )
223
+ ' X-CSRF-Token' : $ ( ' meta[name="csrf-token"]' ) . attr ( ' content' )
170
224
} ,
171
225
beforeSend : function ( ) {
172
226
showSpinner ( ) ;
@@ -183,44 +237,54 @@ window.DropzoneInput = (function() {
183
237
}
184
238
} ) ;
185
239
} ;
240
+
241
+ updateAttachingMessage = ( files , messageContainer ) => {
242
+ let attachingMessage ;
243
+ const filesCount = files . filter ( function ( file ) {
244
+ return file . status === 'uploading' ||
245
+ file . status === 'queued' ;
246
+ } ) . length ;
247
+
248
+ // Dinamycally change uploading files text depending on files number in
249
+ // dropzone files queue.
250
+ if ( filesCount > 1 ) {
251
+ attachingMessage = 'Attaching ' + filesCount + ' files -' ;
252
+ } else {
253
+ attachingMessage = 'Attaching a file -' ;
254
+ }
255
+
256
+ messageContainer . text ( attachingMessage ) ;
257
+ } ;
258
+
186
259
insertToTextArea = function ( filename , url ) {
187
260
return $ ( child ) . val ( function ( index , val ) {
188
- return val . replace ( "{{" + filename + "}}" , url ) ;
261
+ return val . replace ( `{{ ${ filename } }}` , url ) ;
189
262
} ) ;
190
263
} ;
264
+
191
265
appendToTextArea = function ( url ) {
192
266
return $ ( child ) . val ( function ( index , val ) {
193
267
return val + url + "\n" ;
194
268
} ) ;
195
269
} ;
270
+
196
271
showSpinner = function ( e ) {
197
- return form . find ( ".div-dropzone-spinner" ) . css ( {
198
- "opacity" : 0.7 ,
199
- "display" : "inherit"
200
- } ) ;
272
+ return $uploadingProgressContainer . removeClass ( 'hide' ) ;
201
273
} ;
274
+
202
275
closeSpinner = function ( ) {
203
- return form . find ( ".div-dropzone-spinner" ) . css ( {
204
- "opacity" : 0 ,
205
- "display" : "none"
206
- } ) ;
276
+ return $uploadingProgressContainer . addClass ( 'hide' ) ;
207
277
} ;
278
+
208
279
showError = function ( message ) {
209
- var checkIfMsgExists , errorAlert ;
210
- errorAlert = $ ( form ) . find ( '.error-alert' ) ;
211
- checkIfMsgExists = errorAlert . children ( ) . length ;
212
- if ( checkIfMsgExists === 0 ) {
213
- errorAlert . append ( divAlert ) ;
214
- return $ ( ".div-dropzone-alert" ) . append ( btnAlert + message ) ;
215
- }
280
+ $uploadingErrorContainer . removeClass ( 'hide' ) ;
281
+ $uploadingErrorMessage . html ( message ) ;
216
282
} ;
217
- closeAlertMessage = function ( ) {
218
- return form . find ( ".div-dropzone-alert" ) . alert ( "close" ) ;
219
- } ;
220
- form . find ( ".markdown-selector" ) . click ( function ( e ) {
283
+
284
+ form . find ( '.markdown-selector' ) . click ( function ( e ) {
221
285
e . preventDefault ( ) ;
222
286
$ ( this ) . closest ( '.gfm-form' ) . find ( '.div-dropzone' ) . click ( ) ;
223
- form_textarea . focus ( ) ;
287
+ formTextarea . focus ( ) ;
224
288
} ) ;
225
289
}
226
290
0 commit comments