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