1jQuery(function () {
2    'use strict';
3
4    var $lastKnownCaretPosition = 0; // IE 11 fix
5    var $editarea;
6    var $filelisting;
7
8    var didInit = false;
9    var filesThatExist = [];
10    var DW_AJAX_URL = window.DOKU_BASE + 'lib/exe/ajax.php';
11    var ERROR_DIALOG_ID = 'dropfiles_error_dialog';
12    var UPLOAD_PROGRESS_WIDGET_ID = 'plugin_dropfiles_uploadwidget';
13
14    /**
15     * Create a XMLHttpRequest that updates the value of the provided progressbar
16     *
17     * @param {JQuery<Node>} $progressBar jQuery object of the progress-bar
18     * @return {XMLHttpRequest} the XMLHttpRequest expected by jQuery's .ajax()
19     */
20    function createXHRudateProgressBar($progressBar) {
21        var xhr = jQuery.ajaxSettings.xhr();
22        xhr.upload.onprogress = function (ev) {
23            if (ev.lengthComputable) {
24                var percentComplete = ev.loaded / ev.total;
25                $progressBar.progressbar('option', { value: percentComplete });
26            }
27        };
28        return xhr;
29    }
30
31    /**
32     * Remove the first item from the stack of files
33     *
34     * @return {void}
35     */
36    function skipFile() {
37        jQuery('#' + ERROR_DIALOG_ID).remove();
38        filesThatExist.shift();
39        if (filesThatExist.length) {
40            showErrorDialog();
41        }
42    }
43
44    /**
45     * Upload and overwrite the first item from the stack of files
46     *
47     * @return {void}
48     */
49    function overwriteFile() {
50        jQuery('#' + ERROR_DIALOG_ID).remove();
51        uploadFiles([filesThatExist.shift()], true);
52        if (filesThatExist.length) {
53            showErrorDialog();
54        }
55    }
56
57    /**
58     * Upload all remaining files to the server and overwrite the existing files there
59     *
60     * @return {void}
61     */
62    function overwriteAll() {
63        jQuery('#' + ERROR_DIALOG_ID).remove();
64        uploadFiles(filesThatExist, true);
65        filesThatExist = [];
66    }
67
68    /**
69     * Offer to rename the first file in the stack of files
70     *
71     * @return {void}
72     */
73    function renameFile() {
74        var $errorDialog = jQuery('#' + ERROR_DIALOG_ID);
75        var $newInput = jQuery('<form></form>');
76        $newInput.append(jQuery('<input name="filename">').val(filesThatExist[0].name).css('margin-right', '0.4em'));
77        $newInput.append(jQuery('<button name="rename" type="submit">' + window.LANG.plugins.dropfiles.rename + '</button>'));
78        $newInput.append(jQuery('<button name="cancel">' + window.LANG.plugins.dropfiles.cancel + '</button>'));
79        $newInput.find('button').button();
80        $newInput.on('submit', function (event) {
81            event.preventDefault();
82            $errorDialog.remove();
83            var fileToBeUploaded = filesThatExist.shift();
84            fileToBeUploaded.newFileName = $newInput.find('input').val();
85            uploadFiles([fileToBeUploaded]);
86            if (filesThatExist.length) {
87                showErrorDialog();
88            }
89        });
90        $newInput.find('button[name="cancel"]').click(function (event) {
91            event.preventDefault();
92            $errorDialog.remove();
93            showErrorDialog();
94        });
95        $errorDialog.parent().find('.ui-dialog-buttonpane').html($newInput);
96    }
97
98    /**
99     * Create an error dialog and add files to it
100     *
101     * @return {void}
102     */
103    function showErrorDialog() {
104        var fileName = filesThatExist[0].newFileName || filesThatExist[0].name;
105        var text = window.LANG.plugins.dropfiles['popup:fileExists'].replace('%s', fileName);
106        if (fileName !== filesThatExist[0].name) {
107            text += ' ' + window.LANG.plugins.dropfiles['popup:originalName'].replace('%s', filesThatExist[0].name);
108        }
109        var errorTitle = window.LANG.plugins.dropfiles['title:fileExistsError'];
110        var $errorDialog = jQuery('<div id="' + ERROR_DIALOG_ID + '" title="' + errorTitle + '"></div>').text(text).appendTo(jQuery('body'));
111        var buttons = [
112            {
113                text: window.LANG.plugins.dropfiles.skip,
114                click: skipFile
115            },
116            {
117                text: window.LANG.plugins.dropfiles.rename,
118                click: renameFile
119            },
120            {
121                text: window.LANG.plugins.dropfiles.overwrite,
122                click: overwriteFile
123            }
124        ];
125
126        if (filesThatExist.length > 1) {
127            buttons.push(
128                {
129                    text: window.LANG.plugins.dropfiles.overwriteAll,
130                    click: overwriteAll
131                }
132            );
133        }
134        jQuery($errorDialog).dialog({
135            width: 510,
136            buttons: buttons
137        }).draggable();
138        jQuery($errorDialog).dialog('widget').addClass('dropfiles');
139    }
140
141
142    /**
143     * Cancel an event.
144     *
145     * @param {Event} e
146     */
147    function cancelEvent(e) {
148        e.preventDefault();
149        e.stopPropagation();
150    }
151
152
153    /**
154     * Handle drag enter.
155     *
156     * @param {Event} e
157     */
158    function onDragEnter(e) {
159        cancelEvent(e);
160
161        if ($editarea[0].selectionStart !== $lastKnownCaretPosition) {
162            // IE 11 fix
163            $editarea[0].setSelectionRange($lastKnownCaretPosition, $lastKnownCaretPosition);
164        }
165    }
166
167
168    /**
169     * Handle drop.
170     *
171     * @param {Event} e
172     */
173    function onDrop(e) {
174        if (!e.originalEvent.dataTransfer || !e.originalEvent.dataTransfer.files.length) {
175            return;
176        }
177
178        cancelEvent(e);
179
180        var files = e.originalEvent.dataTransfer.files;
181        handleDroppedFiles(files, getNamespaceFromTarget(e.target), this);
182    }
183
184
185    /**
186     * Enable drag'n'drop for the provided elements.
187     *
188     * @param {jQuery} $elements The Elements for which to enable drag and drop
189     *
190     * @return {void}
191     */
192    function enableDragAndDrop($elements) {
193        $elements.off('dragover', cancelEvent).on('dragover', cancelEvent);
194        $elements.off('dragenter', onDragEnter).on('dragenter', onDragEnter);
195        $elements.off('drop', onDrop).on('drop', onDrop);
196    }
197
198    /**
199     *
200     * @param {FileList} files The filelist from the event
201     * @param {string} namespace the namespace in which to upload the files
202     * @param {Node} elementOntoWhichItWasDropped Before this the error messeages will be inserted
203     *
204     * @return {void}
205     */
206    function handleDroppedFiles(files, namespace, elementOntoWhichItWasDropped) {
207        // todo Dateigrößen, Filetypes
208        var filelist = jQuery.makeArray(files).map(
209            function(file) {file.namespace = namespace; return file;}
210        );
211        if (!filelist.length) {
212            return;
213        }
214
215        // check filenames etc.
216        jQuery.post(DW_AJAX_URL, {
217            call: 'dropfiles_checkfiles',
218            sectok: jQuery('input[name="sectok"]').val(),
219            ns: namespace,
220            filenames: filelist.map(function (file) {
221                return file.name;
222            })
223        }).done(function handleCheckFilesResult(json) {
224            var data = JSON.parse(json);
225            var filesWithoutErrors = filelist.filter(function (file) {
226                return data[file.name] === '';
227            });
228            filesThatExist = filelist.filter(function (file) {
229                return data[file.name] === 'file exists';
230            });
231            var filesWithOtherErrors = filelist.filter(function (file) {
232                return data[file.name] && data[file.name] !== 'file exists';
233            });
234
235            // show errors / pending files
236            if (filesWithoutErrors.length) {
237                uploadFiles(filesWithoutErrors);
238            }
239
240            if (filesWithOtherErrors.length) {
241                filesWithOtherErrors.map(function (file) {
242                    var $errorMessage = jQuery('<div class="error"></div>');
243                    $errorMessage.text(file.name + ': ' + data[file.name]);
244                    jQuery(elementOntoWhichItWasDropped).before($errorMessage);
245                });
246            }
247
248            // upload valid files
249            if (filesThatExist.length) {
250                showErrorDialog();
251            }
252
253        });
254    }
255
256    /**
257     * Insert the syntax to the uploaded file into the page
258     *
259     * @param {string} fileid the id of the uploaded file as returned by DokuWiki
260     *
261     * @return {void}
262     */
263    function insertSyntax(fileid) {
264        if (!$editarea.length) {
265            return;
266        }
267        var open = '{{' + fileid;
268        var close = '}}';
269
270        var selection = DWgetSelection($editarea[0]);
271        var text = selection.getText();
272        var opts;
273
274        // don't include trailing space in selection
275        if(text.charAt(text.length - 1) == ' '){
276            selection.end--;
277            text = selection.getText();
278        }
279
280        if(text){
281            text = '|' + text;  // use text as label
282        }
283        opts = { nosel: true };
284        text = open + text + close;
285        pasteText(selection,text,opts);
286    }
287
288    /**
289     *
290     * @param {File[]} filelist List of the files to be uploaded
291     * @param {boolean} [overwrite] should the files be overwritten at the server?
292     *
293     * @return {void}
294     */
295    function uploadFiles(filelist, overwrite) {
296        if (typeof overwrite === 'undefined') {
297            // noinspection AssignmentToFunctionParameterJS
298            overwrite = 0;
299        }
300        var $widget = jQuery('#' + UPLOAD_PROGRESS_WIDGET_ID);
301        $widget.show().dialog({
302            width: 600,
303            close: function clearDoneEntries() {
304                var $uploadBars = $widget.find('.dropfiles_file_upload_bar');
305                var $uploadBarsDone = $uploadBars.filter(function(index, element) {
306                    return jQuery(element).find('.ui-progressbar-complete').length
307                });
308                $uploadBarsDone.remove();
309                $widget.find('.error').remove();
310            }
311        });
312
313        $widget.dialog('widget').addClass('dropfiles');
314        filelist.forEach(function (file) {
315            var fileName = file.newFileName || file.name;
316
317            var $statusbar = jQuery('<div class="dropfiles_file_upload_bar"></div>');
318            $statusbar.append(jQuery('<span class="filename">').text(fileName));
319            var $progressBar = jQuery('<div class="progressbar">').progressbar({ max: 1 });
320            $statusbar.append($progressBar);
321            $widget.append($statusbar);
322            if (!$widget.dialog('isOpen')) {
323                $widget.dialog('open');
324            }
325
326            var form = new FormData();
327            form.append('qqfile', file, fileName);
328            form.append('call', 'dropfiles_mediaupload');
329            form.append('sectok', jQuery('input[name="sectok"]').val());
330            form.append('ns', file.namespace);
331            form.append('ow', overwrite);
332
333            var settings = {
334                'type': 'POST',
335                'data': form,
336                'cache': false,
337                'processData': false,
338                'contentType': false,
339                'xhr': function () {
340                    return createXHRudateProgressBar($progressBar)
341                }
342            };
343
344            jQuery.ajax(DW_AJAX_URL, settings)
345                .done(
346                    function (data) {
347                        if (data.success) {
348                            $progressBar.find('.ui-progressbar-value').css('background-color', 'green');
349                            $statusbar.find('.filename').wrap(jQuery('<a>').attr({
350                                'href': data.link,
351                                'target': '_blank'
352                            }));
353                            if (window.JSINFO.plugins.dropfiles.insertFileLink) {
354                                insertSyntax(data.id);
355                            }
356                            if ($filelisting.length) {
357                                $filelisting.find('.plugin__filelisting_content')
358                                    .trigger('namespaceFilesChanged', file.namespace);
359                            }
360                            return;
361                        }
362                        if (data.errorType === 'file exists') {
363                            $progressBar.find('.ui-progressbar-value').css('background-color', 'red');
364                            filesThatExist.push(file);
365                            if (filesThatExist.length === 1) {
366                                showErrorDialog();
367                            }
368                            var $fileExistsErrorMessage = jQuery('<div class="error"></div>');
369                            $fileExistsErrorMessage.text(fileName + ': ' + data.error);
370                            $fileExistsErrorMessage.insertAfter($statusbar);
371                            return;
372                        }
373                        $progressBar.find('.ui-progressbar-value').css('background-color', 'red');
374                        var $errorMessage = jQuery('<div class="error"></div>');
375                        $errorMessage.text(fileName + ': ' + data.error);
376                        $errorMessage.insertAfter($statusbar);
377                    }
378                )
379                .fail(
380                    function (jqXHR, textStatus, errorThrown) {
381                        console.log('Class: , Function: fail-callback, Line 110 {jqXHR, textStatus, errorThrown}(): '
382                            , { jqXHR: jqXHR, textStatus: textStatus, errorThrown: errorThrown });
383                    }
384                );
385        });
386    }
387
388    /**
389     * If the target is part of the filelisting plugin, return the namespace of the target-row, otherwise the current
390     *
391     * @param {Node} target The Node onto which the files were dropped
392     * @return {string} The namespace referenced by the target or the current namespace
393     */
394    function getNamespaceFromTarget(target) {
395        var $filelisting = jQuery(target).closest('.plugin__filelisting');
396        if ($filelisting.length) {
397            var $targetRow = jQuery(target).closest('tr');
398            return $targetRow.data('namespace') || $targetRow.data('childof') || $filelisting.data('namespace') || window.JSINFO.namespace;
399        }
400        return window.JSINFO.namespace;
401    }
402
403    /**
404     * Wrapper for initial bootstrapping
405     *
406     * @return {void}
407     */
408    function bootstrapFunctionality() {
409        enableDragAndDrop($editarea);
410        enableDragAndDrop($filelisting);
411
412        if (!didInit) {
413            var widgetTitle = window.LANG.plugins.dropfiles['title:fileUpload'];
414            var $widget = jQuery('<div title="' + widgetTitle + '" id="' + UPLOAD_PROGRESS_WIDGET_ID + '"></div>').hide();
415            jQuery('body').append($widget);
416        }
417        didInit = true;
418    }
419
420
421    /**
422     * Called when the edit area loses focus.
423     */
424    function onEditBlur() {
425        // IE 11 fix
426        $lastKnownCaretPosition = $editarea[0].selectionStart;
427    }
428
429
430    /**
431     * Initialize (or reinitialize) the plugin.
432     */
433    function init() {
434        $editarea = jQuery('#wiki__text');
435        $filelisting = jQuery('.plugin__filelisting');
436        $lastKnownCaretPosition = 0; // IE 11 fix
437
438        if ($editarea.length || $filelisting.length) {
439            bootstrapFunctionality();
440
441            $editarea.off('blur', onEditBlur).on('blur', onEditBlur);
442        }
443    }
444
445    init();
446
447    // fastwiki plugin support
448    jQuery(window).on('fastwiki:afterSwitch', function(evt, viewMode, isSectionEdit, prevViewMode) {
449        if (viewMode == 'edit' || isSectionEdit) {
450            init();
451        }
452    });
453});
454