diff --git a/Build/types/TYPO3/index.d.ts b/Build/types/TYPO3/index.d.ts index f0512070a8557d810f121756805008f9adc7d7a7..d114aad49161fb84c1d324776f5dad6bf6609dbd 100644 --- a/Build/types/TYPO3/index.d.ts +++ b/Build/types/TYPO3/index.d.ts @@ -31,6 +31,21 @@ declare namespace TYPO3 { public show(title: string, content: any, severity: number, buttons: any[], additionalCssClasses?: string[]): JQuery; // tslint:disable-line:max-line-length public dismiss(): void; } + export class Notification { + public readonly Notification: { + NOTICE: -2, + INFO: -1, + OK: 0, + WARNING: 1, + ERROR: 2 + }; + public notice(title: string, message: string, duration: Number): string; + public info(title: string, message: string, duration: Number): string; + public success(title: string, message: string, duration: Number): string; + public warning(title: string, message: string, duration: Number): string; + public error(title: string, message: string, duration: Number): string; + public showMessage(title: string, message: string, severity: Number, duration?: Number): string; + } export class Severity { public readonly notice: number; public readonly info: number; @@ -59,6 +74,10 @@ declare module 'TYPO3/CMS/Backend/Modal' { export = new TYPO3.CMS.Backend.Modal(); } +declare module 'TYPO3/CMS/Backend/Notification' { + export = new TYPO3.CMS.Backend.Notification(); +} + declare module 'TYPO3/CMS/Backend/Severity' { export = new TYPO3.CMS.Backend.Severity(); } @@ -67,6 +86,9 @@ declare module 'TYPO3/CMS/Backend/Severity' { interface Window { TYPO3: any; $: any; + inline: { + delayedImportElement: (objectId: number, table: string, uid: number, type: string) => void + }; } /** @@ -78,10 +100,32 @@ declare module 'TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min' { } declare module 'cm/lib/codemirror'; +declare module 'moment'; +declare module 'TYPO3/CMS/Backend/jsfunc.inline'; + +/** + * Options for the plugin. + * TODO fix this + */ +interface DragUploaderOptions { + /** + * CSS selector for the element where generated messages are inserted. (required) + */ + outputSelector: string; + /** + * Color of the message text. (optional) + */ + outputColor?: string; +} + +interface JQueryTypedEvent<T extends Event> extends JQueryEventObject { + originalEvent: T; +} /** * Required to make jQuery plugins "available" in TypeScript */ interface JQuery { clearable(): JQuery; + dragUploader(options?: DragUploaderOptions): JQuery; } diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/DragUploader.ts b/typo3/sysext/backend/Resources/Private/TypeScript/DragUploader.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1e7a326477fc29463f3b45ee1948c4f59fe5465 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/DragUploader.ts @@ -0,0 +1,749 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * Module: TYPO3/CMS/Backend/DragUploader + */ + +import * as $ from 'jquery'; +import moment = require('moment'); +import NProgress = require('nprogress'); +import Modal = require('TYPO3/CMS/Backend/Modal'); +import Notification = require('TYPO3/CMS/Backend/Notification'); +import Severity = require('TYPO3/CMS/Backend/Severity'); + +/** + * Possible actions for conflicts w/ existing files + */ +enum Action { + OVERRIDE = 'replace', + RENAME = 'rename', + SKIP = 'cancel', + USE_EXISTING = 'useExisting' +} + +/** + * Properties of a file as returned from the AJAX action; essential, this is a serialized instance of + * \TYPO3\CMS\Core\Resource\File plus some extra properties (see FileController::flattenResultDataValue()) + */ +interface UploadedFile { + name: string; + id: number; + uid: number; + icon: string; + extension: string; + permissions: {read: boolean; write: boolean}; + size: number; + // formatted as ddmmyy + date: string; + + mtime: Date; + thumbUrl: string; + type: string; +} + +class DragUploaderPlugin { + public irreObjectUid: number; + public $fileList: JQuery; + public fileListColumnCount: number; + public filesExtensionsAllowed: string; + public fileDenyPattern: RegExp | null; + public maxFileSize: number; + public $trigger: JQuery; + public target: string; + + /** + * Array of files which are asked for being overridden + */ + private askForOverride: Array<{original: UploadedFile, uploaded: File, action: Action}> = []; + + private percentagePerFile: number = 1; + + private $body: JQuery; + private $element: JQuery; + private $dropzone: JQuery; + private $dropzoneMask: JQuery; + private fileInput: HTMLInputElement; + private browserCapabilities: { fileReader: boolean; DnD: boolean; Progress: boolean }; + private dropZoneInsertBefore: boolean; + private queueLength: number; + + constructor(element: HTMLElement) { + this.$body = $('body'); + this.$element = $(element); + const hasTrigger = this.$element.data('dropzoneTrigger') !== undefined; + this.$trigger = $(this.$element.data('dropzoneTrigger')); + this.$dropzone = $('<div />').addClass('dropzone').hide(); + this.irreObjectUid = this.$element.data('fileIrreObject'); + + const dropZoneEscapedTarget = this.$element.data('dropzoneTarget'); + if (this.irreObjectUid && this.$element.nextAll(dropZoneEscapedTarget).length !== 0) { + this.dropZoneInsertBefore = true; + this.$dropzone.insertBefore(dropZoneEscapedTarget); + } else { + this.dropZoneInsertBefore = false; + this.$dropzone.insertAfter(dropZoneEscapedTarget); + } + this.$dropzoneMask = $('<div />').addClass('dropzone-mask').appendTo(this.$dropzone); + this.fileInput = <HTMLInputElement>document.createElement('input'); + this.fileInput.setAttribute('type', 'file'); + this.fileInput.setAttribute('multiple', 'multiple'); + this.fileInput.setAttribute('name', 'files[]'); + this.fileInput.classList.add('upload-file-picker'); + this.$body.append(this.fileInput); + + this.$fileList = $(this.$element.data('progress-container')); + this.fileListColumnCount = $('thead tr:first th', this.$fileList).length; + this.filesExtensionsAllowed = this.$element.data('file-allowed'); + this.fileDenyPattern = this.$element.data('file-deny-pattern') ? new RegExp(this.$element.data('file-deny-pattern'), 'i') : null; + this.maxFileSize = parseInt(this.$element.data('max-file-size'), 10); + this.target = this.$element.data('target-folder'); + + this.browserCapabilities = { + fileReader: typeof FileReader !== 'undefined', + DnD: 'draggable' in document.createElement('span'), + Progress: 'upload' in new XMLHttpRequest + }; + + + if (!this.browserCapabilities.DnD) { + console.warn('Browser has no Drag and drop capabilities; cannot initialize DragUploader'); + return; + } + + this.$body.on('dragover', this.dragFileIntoDocument); + this.$body.on('dragend', this.dragAborted); + this.$body.on('drop', this.ignoreDrop); + + this.$dropzone.on('dragenter', this.fileInDropzone); + this.$dropzoneMask.on('dragenter', this.fileInDropzone); + this.$dropzoneMask.on('dragleave', this.fileOutOfDropzone); + this.$dropzoneMask.on('drop', (ev: JQueryEventObject) => this.handleDrop(<JQueryTypedEvent<DragEvent>>ev)); + + this.$dropzone.prepend( + '<div class="dropzone-hint">' + + '<div class="dropzone-hint-media">' + + '<div class="dropzone-hint-icon"></div>' + + '</div>' + + '<div class="dropzone-hint-body">' + + '<h3 class="dropzone-hint-title">' + + TYPO3.lang['file_upload.dropzonehint.title'] + + '</h3>' + + '<p class="dropzone-hint-message">' + + TYPO3.lang['file_upload.dropzonehint.message'] + + '</p>' + + '</div>' + + '</div>' + ).click(() => { + this.fileInput.click(); + }); + $('<span />').addClass('dropzone-close').click(this.hideDropzone).appendTo(this.$dropzone); + + // no filelist then create own progress table + if (this.$fileList.length === 0) { + this.$fileList = $('<table />') + .attr('id', 'typo3-filelist') + .addClass('table table-striped table-hover upload-queue') + .html('<tbody></tbody>').hide(); + + if (this.dropZoneInsertBefore) { + this.$fileList.insertAfter(this.$dropzone); + } else { + this.$fileList.insertBefore(this.$dropzone); + } + this.fileListColumnCount = 7; + } + + this.fileInput.addEventListener('change', () => { + this.processFiles(Array.apply(null, this.fileInput.files)); + }); + + this.bindUploadButton(hasTrigger === true ? this.$trigger : this.$element); + } + + public showDropzone(): void { + this.$dropzone.show(); + } + + /** + * + * @param {Event} event + */ + public hideDropzone(event: Event): void { + event.stopPropagation(); + event.preventDefault(); + this.$dropzone.hide(); + } + + /** + * @param {Event} event + * @returns {boolean} + */ + public dragFileIntoDocument = (event: Event): boolean => { + event.stopPropagation(); + event.preventDefault(); + $(event.currentTarget).addClass('drop-in-progress'); + this.showDropzone(); + return false; + } + + /** + * + * @param {Event} event + * @returns {Boolean} + */ + public dragAborted = (event: Event): boolean => { + event.stopPropagation(); + event.preventDefault(); + $(event.currentTarget).removeClass('drop-in-progress'); + return false; + } + + public ignoreDrop = (event: Event): boolean => { + // stops the browser from redirecting. + event.stopPropagation(); + event.preventDefault(); + this.dragAborted(event); + return false; + } + + public handleDrop = (event: JQueryTypedEvent<DragEvent>): void => { + this.ignoreDrop(event); + this.processFiles(event.originalEvent.dataTransfer.files); + this.$dropzone.removeClass('drop-status-ok'); + } + + /** + * @param {FileList} files + */ + public processFiles(files: FileList): void { + this.queueLength = files.length; + + if (!this.$fileList.is(':visible')) { + this.$fileList.show(); + } + + NProgress.start(); + this.percentagePerFile = 1 / files.length; + + // Check for each file if is already exist before adding it to the queue + const ajaxCalls: JQueryXHR[] = []; + $.each(files, (i: string, file) => { + ajaxCalls[parseInt(i, 10)] = $.ajax({ + url: TYPO3.settings.ajaxUrls.file_exists, + data: { + fileName: file.name, + fileTarget: this.target + }, + cache: false, + success: (response: any) => { + const fileExists = typeof response.uid !== 'undefined'; + if (fileExists) { + this.askForOverride.push({ + original: response, + uploaded: file, + action: this.irreObjectUid ? Action.USE_EXISTING : Action.SKIP + }); + NProgress.inc(this.percentagePerFile); + } else { + // Unused var _ is necessary as "no-unused-expression" is active + const _ = new FileQueueItem(this, file, Action.SKIP); + } + } + }); + }); + + $.when.apply($, ajaxCalls).done(() => { + this.drawOverrideModal(); + NProgress.done(); + }); + + this.fileInput.value = ''; + } + + public fileInDropzone = (): void => { + this.$dropzone.addClass('drop-status-ok'); + } + + public fileOutOfDropzone = (): void => { + this.$dropzone.removeClass('drop-status-ok'); + } + + /** + * Bind file picker to default upload button + * + * @param {Object} button + */ + public bindUploadButton(button: JQuery): void { + button.click((event: Event) => { + event.preventDefault(); + this.fileInput.click(); + this.showDropzone(); + }); + } + + /** + * Decrements the queue and renders a flash message if queue is empty + */ + public decrementQueueLength(): void { + if (this.queueLength > 0) { + this.queueLength--; + if (this.queueLength === 0) { + $.ajax({ + url: TYPO3.settings.ajaxUrls.flashmessages_render, + cache: false, + success: (data) => { + $.each(data, (index: number, flashMessage: {title: string, message: string, severity: number}) => { + Notification.showMessage(flashMessage.title, flashMessage.message, flashMessage.severity); + }); + } + }); + } + } + } + + /** + * Renders the modal for existing files + */ + public drawOverrideModal(): void { + const amountOfItems = Object.keys(this.askForOverride).length; + if (amountOfItems === 0) { + return; + } + const $modalContent = $('<div/>').append( + $('<p/>').text(TYPO3.lang['file_upload.existingfiles.description']), + $('<table/>', {class: 'table'}).append( + $('<thead/>').append( + $('<tr />').append( + $('<th/>'), + $('<th/>').text(TYPO3.lang['file_upload.header.originalFile']), + $('<th/>').text(TYPO3.lang['file_upload.header.uploadedFile']), + $('<th/>').text(TYPO3.lang['file_upload.header.action']) + ) + ) + ) + ); + + for (let i = 0; i < amountOfItems; ++i) { + const $record = $('<tr />').append( + $('<td />').append( + (this.askForOverride[i].original.thumbUrl !== '' + ? $('<img />', {src: this.askForOverride[i].original.thumbUrl, height: 40}) + : $(this.askForOverride[i].original.icon) + ) + ), + $('<td />').html( + this.askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(this.askForOverride[i].uploaded.size)) + ')' + + '<br>' + moment(this.askForOverride[i].uploaded.lastModifiedDate, 'x').format('YYYY-MM-DD HH:mm') + ), + $('<td />').html( + this.askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(this.askForOverride[i].original.size)) + ')' + + '<br>' + moment(this.askForOverride[i].original.mtime, 'X').format('YYYY-MM-DD HH:mm') + ), + $('<td />').append( + $('<select />', {class: 'form-control t3js-actions', 'data-override': i}).append( + (this.irreObjectUid ? $('<option/>').val(Action.USE_EXISTING).text(TYPO3.lang['file_upload.actions.use_existing']) : ''), + $('<option />').val(Action.SKIP).text(TYPO3.lang['file_upload.actions.skip']), + $('<option />').val(Action.RENAME).text(TYPO3.lang['file_upload.actions.rename']), + $('<option />').val(Action.OVERRIDE).text(TYPO3.lang['file_upload.actions.override']) + ) + ) + ); + $modalContent.find('table').append('<tbody />').append($record); + } + + const $modal = Modal.confirm( + TYPO3.lang['file_upload.existingfiles.title'], $modalContent, Severity.warning, + [ + { + text: $(this).data('button-close-text') || TYPO3.lang['file_upload.button.cancel'] || 'Cancel', + active: true, + btnClass: 'btn-default', + name: 'cancel' + }, + { + text: $(this).data('button-ok-text') || TYPO3.lang['file_upload.button.continue'] || 'Continue with selected actions', + btnClass: 'btn-warning', + name: 'continue' + } + ], + ['modal-inner-scroll'] + ); + $modal.find('.modal-dialog').addClass('modal-lg'); + + $modal.find('.modal-footer').prepend( + $('<span/>').addClass('form-inline').append( + $('<label/>').text(TYPO3.lang['file_upload.actions.all.label']), + $('<select/>', {class: 'form-control t3js-actions-all'}).append( + $('<option/>').val('').text(TYPO3.lang['file_upload.actions.all.empty']), + (this.irreObjectUid ? $('<option/>').val(Action.USE_EXISTING).text(TYPO3.lang['file_upload.actions.all.use_existing']) : ''), + $('<option/>').val(Action.SKIP).text(TYPO3.lang['file_upload.actions.all.skip']), + $('<option/>').val(Action.RENAME).text(TYPO3.lang['file_upload.actions.all.rename']), + $('<option/>').val(Action.OVERRIDE).text(TYPO3.lang['file_upload.actions.all.override']) + ) + ) + ); + + const uploader = this; + $modal.on('change', '.t3js-actions-all', function(this: HTMLInputElement): void { + const $this = $(this), + value = $this.val(); + + if (value !== '') { + // mass action was selected, apply action to every file + $modal.find('.t3js-actions').each((i, select) => { + const $select = $(select), + index = parseInt($select.data('override'), 10); + $select.val(value).prop('disabled', 'disabled'); + uploader.askForOverride[index].action = <Action>$select.val(); + }); + } else { + $modal.find('.t3js-actions').removeProp('disabled'); + } + }).on('change', '.t3js-actions', function(this: HTMLInputElement): void { + const $this = $(this), + index = parseInt($this.data('override'), 10); + uploader.askForOverride[index].action = <Action>$this.val(); + }).on('button.clicked', function(this: HTMLInputElement, e: Event): void { + if ((<HTMLInputElement>(e.target)).name === 'cancel') { + uploader.askForOverride = []; + Modal.dismiss(); + } else if ((<HTMLInputElement>(e.target)).name === 'continue') { + $.each(uploader.askForOverride, (key, fileInfo) => { + if (fileInfo.action === Action.USE_EXISTING) { + DragUploader.addFileToIrre( + uploader.irreObjectUid, + fileInfo.original + ); + } else if (fileInfo.action !== Action.SKIP) { + // Unused var _ is necessary as "no-unused-expression" is active + const _ = new FileQueueItem(uploader, fileInfo.uploaded, fileInfo.action); + } + }); + uploader.askForOverride = []; + Modal.dismiss(); + } + }).on('hidden.bs.modal', () => { + this.askForOverride = []; + }); + } +} + +class FileQueueItem { + private $row: JQuery; + private $iconCol: JQuery; + private $fileName: JQuery; + private $progress: JQuery; + private $progressContainer: JQuery; + private $progressBar: JQuery; + private $progressPercentage: JQuery; + private $progressMessage: JQuery; + private dragUploader: DragUploaderPlugin; + private file: File; + private override: Action; + private upload: XMLHttpRequest; + + constructor(dragUploader: DragUploaderPlugin, file: File, override: Action) { + this.dragUploader = dragUploader; + this.file = file; + this.override = override; + + this.$row = $('<tr />').addClass('upload-queue-item uploading'); + this.$iconCol = $('<td />').addClass('col-icon').appendTo(this.$row); + this.$fileName = $('<td />').text(file.name).appendTo(this.$row); + this.$progress = $('<td />').attr('colspan', this.dragUploader.fileListColumnCount - 2).appendTo(this.$row); + this.$progressContainer = $('<div />').addClass('upload-queue-progress').appendTo(this.$progress); + this.$progressBar = $('<div />').addClass('upload-queue-progress-bar').appendTo(this.$progressContainer); + this.$progressPercentage = $('<span />').addClass('upload-queue-progress-percentage').appendTo(this.$progressContainer); + this.$progressMessage = $('<span />').addClass('upload-queue-progress-message').appendTo(this.$progressContainer); + + + // position queue item in filelist + if ($('tbody tr.upload-queue-item', this.dragUploader.$fileList).length === 0) { + this.$row.prependTo($('tbody', this.dragUploader.$fileList)); + this.$row.addClass('last'); + } else { + this.$row.insertBefore($('tbody tr.upload-queue-item:first', this.dragUploader.$fileList)); + } + + // set dummy file icon + this.$iconCol.html('<span class="t3-icon t3-icon-mimetypes t3-icon-other-other"> </span>'); + + // check file size + if (this.dragUploader.maxFileSize > 0 && this.file.size > this.dragUploader.maxFileSize) { + this.updateMessage(TYPO3.lang['file_upload.maxFileSizeExceeded'] + .replace(/\{0\}/g, this.file.name) + .replace(/\{1\}/g, DragUploader.fileSizeAsString(this.dragUploader.maxFileSize))); + this.$row.addClass('error'); + + // check filename/extension against deny pattern + } else if (this.dragUploader.fileDenyPattern && this.file.name.match(this.dragUploader.fileDenyPattern)) { + this.updateMessage(TYPO3.lang['file_upload.fileNotAllowed'].replace(/\{0\}/g, this.file.name)); + this.$row.addClass('error'); + + } else if (!this.checkAllowedExtensions()) { + this.updateMessage(TYPO3.lang['file_upload.fileExtensionExpected'] + .replace(/\{0\}/g, this.dragUploader.filesExtensionsAllowed) + ); + this.$row.addClass('error'); + } else { + this.updateMessage('- ' + DragUploader.fileSizeAsString(this.file.size)); + + const formData = new FormData(); + formData.append('data[upload][1][target]', this.dragUploader.target); + formData.append('data[upload][1][data]', '1'); + formData.append('overwriteExistingFiles', this.override); + formData.append('redirect', ''); + formData.append('upload_1', this.file); + + const s = $.extend(true, {}, $.ajaxSettings, { + url: TYPO3.settings.ajaxUrls.file_process, + contentType: false, + processData: false, + data: formData, + cache: false, + type: 'POST', + success: (data: {upload?: UploadedFile[]}) => this.uploadSuccess(data), + error: (response: XMLHttpRequest) => this.uploadError(response) + }); + + s.xhr = () => { + const xhr = $.ajaxSettings.xhr(); + xhr.upload.addEventListener('progress', (e: ProgressEvent) => this.updateProgress(e)); + return xhr; + }; + + // start upload + this.upload = $.ajax(s); + } + } + + /** + * @param {string} message + */ + public updateMessage(message: string): void { + this.$progressMessage.text(message); + } + + /** + * Remove the progress bar + */ + public removeProgress(): void { + if (this.$progress) { + this.$progress.remove(); + } + } + + public uploadStart(): void { + this.$progressPercentage.text('(0%)'); + this.$progressBar.width('1%'); + this.dragUploader.$trigger.trigger('uploadStart', [this]); + } + + /** + * @param {XMLHttpRequest} response + */ + public uploadError(response: XMLHttpRequest): void { + this.updateMessage(TYPO3.lang['file_upload.uploadFailed'].replace(/\{0\}/g, this.file.name)); + const error = $(response.responseText); + if (error.is('t3err')) { + this.$progressPercentage.text(error.text()); + } else { + this.$progressPercentage.text('(' + response.statusText + ')'); + } + this.$row.addClass('error'); + this.dragUploader.decrementQueueLength(); + this.dragUploader.$trigger.trigger('uploadError', [this, response]); + } + + /** + * @param {ProgressEvent} event + */ + public updateProgress(event: ProgressEvent): void { + const percentage = Math.round((event.loaded / event.total) * 100) + '%'; + this.$progressBar.outerWidth(percentage); + this.$progressPercentage.text(percentage); + this.dragUploader.$trigger.trigger('updateProgress', [this, percentage, event]); + } + + /** + * @param {{upload?: UploadedFile[]}} data + */ + public uploadSuccess(data: {upload?: UploadedFile[]}): void { + if (data.upload) { + this.dragUploader.decrementQueueLength(); + this.$row.removeClass('uploading'); + this.$fileName.text(data.upload[0].name); + this.$progressPercentage.text(''); + this.$progressMessage.text('100%'); + this.$progressBar.outerWidth('100%'); + + // replace file icon + if (data.upload[0].icon) { + this.$iconCol + .html( + '<a href="#" class="t3js-contextmenutrigger" data-uid="' + + data.upload[0].id + '" data-table="sys_file">' + + data.upload[0].icon + ' </span></a>' + ); + } + + if (this.dragUploader.irreObjectUid) { + DragUploader.addFileToIrre( + this.dragUploader.irreObjectUid, + data.upload[0] + ); + setTimeout( + () => { + this.$row.remove(); + if ($('tr', this.dragUploader.$fileList).length === 0) { + this.dragUploader.$fileList.hide(); + this.dragUploader.$trigger.trigger('uploadSuccess', [this, data]); + } + }, + 3000); + } else { + setTimeout( + () => { + this.showFileInfo(data.upload[0]); + this.dragUploader.$trigger.trigger('uploadSuccess', [this, data]); + }, + 3000); + } + } + } + + /** + * @param {UploadedFile} fileInfo + */ + public showFileInfo(fileInfo: UploadedFile): void { + this.removeProgress(); + // add spacing cells when clibboard and/or extended view is enabled + for (let i = 7; i < this.dragUploader.fileListColumnCount; i++) { + $('<td />').text('').appendTo(this.$row); + } + $('<td />').text(fileInfo.extension.toUpperCase()).appendTo(this.$row); + $('<td />').text(fileInfo.date).appendTo(this.$row); + $('<td />').text(DragUploader.fileSizeAsString(fileInfo.size)).appendTo(this.$row); + let permissions = ''; + if (fileInfo.permissions.read) { + permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.read'] + '</strong>'; + } + if (fileInfo.permissions.write) { + permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.write'] + '</strong>'; + } + $('<td />').html(permissions).appendTo(this.$row); + $('<td />').text('-').appendTo(this.$row); + } + + public checkAllowedExtensions(): boolean { + if (!this.dragUploader.filesExtensionsAllowed) { + return true; + } + const extension = this.file.name.split('.').pop(); + const allowed = this.dragUploader.filesExtensionsAllowed.split(','); + + return $.inArray(extension.toLowerCase(), allowed) !== -1; + } +} + +class DragUploader { + private static options: DragUploaderOptions; + fileListColumnCount: number; + filesExtensionsAllowed: string; + fileDenyPattern: string; + + public static fileSizeAsString(size: number): string { + const sizeKB: number = size / 1024; + let str = ''; + + if (sizeKB > 1024) { + str = (sizeKB / 1024).toFixed(1) + ' MB'; + } else { + str = sizeKB.toFixed(1) + ' KB'; + } + return str; + } + + /** + * @param {number} irre_object + * @param {UploadedFile} file + */ + public static addFileToIrre(irre_object: number, file: UploadedFile): void { + window.inline.delayedImportElement( + irre_object, + 'sys_file', + file.uid, + 'file' + ); + } + + public static init(): void { + const me = this; + const opts = me.options; + + // register the jQuery plugin "DragUploaderPlugin" + $.fn.extend({ + dragUploader: function (options?: DragUploaderOptions | string): JQuery { + return this.each((index: number, elem: HTMLElement): void => { + const $this = $(elem); + let data = $this.data('DragUploaderPlugin'); + if (!data) { + $this.data('DragUploaderPlugin', (data = new DragUploaderPlugin(elem))); + } + if (typeof options === 'string') { + data[options](); + } + }); + } + }); + + $(() => { + $('.t3js-drag-uploader').dragUploader(opts); + }); + } + +} + +/** + * Function to apply the example plugin to the selected elements of a jQuery result. + */ +interface DragUploaderFunction { + /** + * Apply the example plugin to the elements selected in the jQuery result. + * + * @param options Options to use for this application of the example plugin. + * @returns jQuery result. + */ + (options: DragUploaderOptions): JQuery; +} + +export const initialize = function(): void { + DragUploader.init(); + + // load required modules to hook in the post initialize function + if ( + 'undefined' !== typeof TYPO3.settings + && 'undefined' !== typeof TYPO3.settings.RequireJS + && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules + && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'] + ) { + $.each( + TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], (pos, moduleName) => { + require([moduleName]); + } + ); + } +}; + +initialize(); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js b/typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js index bf7c2cf550e20b4a01e24d32abb942a753d1138d..ac35b7727b52923d0f6701b1e1ae20a6a095ae9f 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js @@ -10,673 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ - -/** - * Module: TYPO3/CMS/Backend/DragUploader - * - */ -define(['jquery', - 'moment', - 'nprogress', - 'TYPO3/CMS/Backend/Modal', - 'TYPO3/CMS/Backend/Notification', - 'TYPO3/CMS/Backend/Severity', - 'TYPO3/CMS/Lang/Lang' -], function($, moment, NProgress, Modal, Notification, Severity) { - - /** - * Array of files which are asked for being overridden - * - * @type {array} - */ - var askForOverride = [], - percentagePerFile = 1; - - /** - * File actions - */ - var actions = { - OVERRIDE: 'replace', - RENAME: 'rename', - SKIP: 'cancel', - USE_EXISTING: 'useExisting' - }; - - /* - * part 1: a generic jQuery plugin "$.dragUploader" - */ - - // register the constructor - /** - * - * @param {HTMLElement} element - * @constructor - * @exports TYPO3/CMS/Backend/DragUploader - */ - var DragUploaderPlugin = function(element) { - var me = this; - me.$body = $('body'); - me.$element = $(element); - me.$trigger = $(me.$element.data('dropzoneTrigger')); - me.$dropzone = $('<div />').addClass('dropzone').hide(); - me.irreObjectUid = me.$element.data('fileIrreObject'); - var dropZoneEscapedTarget = me.$element.data('dropzoneTarget'); - if (me.irreObjectUid && me.$element.nextAll(dropZoneEscapedTarget).length !== 0) { - me.dropZoneInsertBefore = true; - me.$dropzone.insertBefore(dropZoneEscapedTarget); - } else { - me.dropZoneInsertBefore = false; - me.$dropzone.insertAfter(dropZoneEscapedTarget); - } - me.$dropzoneMask = $('<div />').addClass('dropzone-mask').appendTo(me.$dropzone); - me.$fileInput = $('<input type="file" multiple name="files[]" />').addClass('upload-file-picker').appendTo(me.$body); - me.$fileList = $(me.$element.data('progress-container')); - me.fileListColumnCount = $('thead tr:first th', me.$fileList).length; - me.filesExtensionsAllowed = me.$element.data('file-allowed'); - me.fileDenyPattern = me.$element.data('file-deny-pattern') ? new RegExp(me.$element.data('file-deny-pattern'), 'i') : false; - me.maxFileSize = parseInt(me.$element.data('max-file-size')); - me.target = me.$element.data('target-folder'); - - me.browserCapabilities = { - fileReader: typeof FileReader !== 'undefined', - DnD: 'draggable' in document.createElement('span'), - FormData: !!window.FormData, - Progress: "upload" in new XMLHttpRequest - }; - - /** - * - */ - me.showDropzone = function() { - me.$dropzone.show(); - }; - - /** - * - * @param {Event} event - */ - me.hideDropzone = function(event) { - event.stopPropagation(); - event.preventDefault(); - me.$dropzone.hide(); - }; - - /** - * - * @param {Event} event - * @returns {Boolean} - */ - me.dragFileIntoDocument = function(event) { - event.stopPropagation(); - event.preventDefault(); - me.$body.addClass('drop-in-progress'); - me.showDropzone(); - return false; - }; - - /** - * - * @param {Event} event - * @returns {Boolean} - */ - me.dragAborted = function(event) { - event.stopPropagation(); - event.preventDefault(); - me.$body.removeClass('drop-in-progress'); - return false; - }; - - /** - * - * @param {Event} event - * @returns {Boolean} - */ - me.ignoreDrop = function(event) { - // stops the browser from redirecting. - event.stopPropagation(); - event.preventDefault(); - me.dragAborted(event); - return false; - }; - - /** - * - * @param {Event} event - */ - me.handleDrop = function(event) { - me.ignoreDrop(event); - me.processFiles(event.originalEvent.dataTransfer.files); - me.$dropzone.removeClass('drop-status-ok'); - }; - - /** - * - * @param {Array} files - */ - me.processFiles = function(files) { - me.queueLength = files.length; - - if (!me.$fileList.is(':visible')) { - me.$fileList.show(); - } - - NProgress.start(); - percentagePerFile = 1 / files.length; - - // Check for each file if is already exist before adding it to the queue - var ajaxCalls = []; - $.each(files, function(i, file) { - - ajaxCalls[i] = $.ajax({ - url: TYPO3.settings.ajaxUrls['file_exists'], - data: { - fileName: file.name, - fileTarget: me.target - }, - cache: false, - success: function(response) { - var fileExists = typeof response.uid !== 'undefined'; - if (fileExists) { - askForOverride.push({ - original: response, - uploaded: file, - action: me.irreObjectUid ? actions.USE_EXISTING : actions.SKIP - }); - NProgress.inc(percentagePerFile); - } else { - new FileQueueItem(me, file, 'cancel'); - } - } - }); - }); - - $.when.apply($, ajaxCalls).done(function() { - me.drawOverrideModal(); - NProgress.done(); - }); - - delete ajaxCalls; - me.$fileInput.val(''); - }; - - /** - * - * @param {Event} event - */ - me.fileInDropzone = function(event) { - me.$dropzone.addClass('drop-status-ok'); - }; - - /** - * - * @param {Event} event - */ - me.fileOutOfDropzone = function(event) { - me.$dropzone.removeClass('drop-status-ok'); - }; - - /** - * bind file picker to default upload button - * - * @param {Object} button - */ - me.bindUploadButton = function(button) { - button.click(function(event) { - event.preventDefault(); - me.$fileInput.click(); - me.showDropzone(); - }); - }; - - if (me.browserCapabilities.DnD) { - me.$body.on('dragover', me.dragFileIntoDocument); - me.$body.on('dragend', me.dragAborted); - me.$body.on('drop', me.ignoreDrop); - - me.$dropzone.on('dragenter', me.fileInDropzone); - me.$dropzoneMask.on('dragenter', me.fileInDropzone); - me.$dropzoneMask.on('dragleave', me.fileOutOfDropzone); - me.$dropzoneMask.on('drop', me.handleDrop); - - me.$dropzone.prepend( - '<div class="dropzone-hint">' + - '<div class="dropzone-hint-media">' + - '<div class="dropzone-hint-icon"></div>' + - '</div>' + - '<div class="dropzone-hint-body">' + - '<h3 class="dropzone-hint-title">' + - TYPO3.lang['file_upload.dropzonehint.title'] + - '</h3>' + - '<p class="dropzone-hint-message">' + - TYPO3.lang['file_upload.dropzonehint.message'] + - '</p>' + - '</div>' + - '</div>').click(function() { - me.$fileInput.click() - }); - $('<span />').addClass('dropzone-close').click(me.hideDropzone).appendTo(me.$dropzone); - - // no filelist then create own progress table - if (me.$fileList.length === 0) { - me.$fileList = $('<table />') - .attr('id', 'typo3-filelist') - .addClass('table table-striped table-hover upload-queue') - .html('<tbody></tbody>').hide(); - if (me.dropZoneInsertBefore) { - me.$fileList.insertAfter(me.$dropzone); - } else { - me.$fileList.insertBefore(me.$dropzone); - } - me.fileListColumnCount = 7; - } - - me.$fileInput.on('change', function() { - me.processFiles(this.files); - }); - - me.bindUploadButton(me.$trigger.length ? me.$trigger : me.$element); - } - - /** - * - */ - me.decrementQueueLength = function() { - if (me.queueLength > 0) { - me.queueLength--; - if (me.queueLength === 0) { - $.ajax({ - url: TYPO3.settings.ajaxUrls['flashmessages_render'], - cache: false, - success: function(data) { - $.each(data, function(index, flashMessage) { - Notification.showMessage(flashMessage.title, flashMessage.message, flashMessage.severity); - }); - } - }); - } - } - }; - - /** - * - */ - me.drawOverrideModal = function() { - var amountOfItems = Object.keys(askForOverride).length; - if (amountOfItems === 0) { - return; - } - var $modalContent = $('<div/>').append( - $('<p/>').text(TYPO3.lang['file_upload.existingfiles.description']), - $('<table/>', {class: 'table'}).append( - $('<thead/>').append( - $('<tr />').append( - $('<th/>'), - $('<th/>').text(TYPO3.lang['file_upload.header.originalFile']), - $('<th/>').text(TYPO3.lang['file_upload.header.uploadedFile']), - $('<th/>').text(TYPO3.lang['file_upload.header.action']) - ) - ) - ) - ); - - for (var i = 0; i < amountOfItems; ++i) { - var $record = $('<tr />').append( - $('<td />').append( - (askForOverride[i].original.thumbUrl !== '' - ? $('<img />', {src: askForOverride[i].original.thumbUrl, height: 40}) - : $(askForOverride[i].original.icon) - ) - ), - $('<td />').html( - askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(askForOverride[i].uploaded.size)) + ')' + - '<br>' + moment(askForOverride[i].uploaded.lastModified, 'x').format('YYYY-MM-DD HH:mm') - ), - $('<td />').html( - askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(askForOverride[i].original.size)) + ')' + - '<br>' + moment(askForOverride[i].original.mtime, 'X').format('YYYY-MM-DD HH:mm') - ), - $('<td />').append( - $('<select />', {class: 'form-control t3js-actions', 'data-override': i}).append( - (me.irreObjectUid ? $('<option/>').val(actions.USE_EXISTING).text(TYPO3.lang['file_upload.actions.use_existing']) : ''), - $('<option />').val(actions.SKIP).text(TYPO3.lang['file_upload.actions.skip']), - $('<option />').val(actions.RENAME).text(TYPO3.lang['file_upload.actions.rename']), - $('<option />').val(actions.OVERRIDE).text(TYPO3.lang['file_upload.actions.override']) - ) - ) - ); - $modalContent.find('table').append('<tbody />').append($record); - } - - var $modal = Modal.confirm(TYPO3.lang['file_upload.existingfiles.title'], $modalContent, Severity.warning, [ - { - text: $(this).data('button-close-text') || TYPO3.lang['file_upload.button.cancel'] || 'Cancel', - active: true, - btnClass: 'btn-default', - name: 'cancel' - }, - { - text: $(this).data('button-ok-text') || TYPO3.lang['file_upload.button.continue'] || 'Continue with selected actions', - btnClass: 'btn-warning', - name: 'continue' - } - ], ['modal-inner-scroll']); - $modal.find('.modal-dialog').addClass('modal-lg'); - - $modal.find('.modal-footer').prepend( - $('<span/>').addClass('form-inline').append( - $('<label/>').text(TYPO3.lang['file_upload.actions.all.label']), - $('<select/>', {class: 'form-control t3js-actions-all'}).append( - $('<option/>').val('').text(TYPO3.lang['file_upload.actions.all.empty']), - (me.irreObjectUid ? $('<option/>').val(actions.USE_EXISTING).text(TYPO3.lang['file_upload.actions.all.use_existing']) : ''), - $('<option/>').val(actions.SKIP).text(TYPO3.lang['file_upload.actions.all.skip']), - $('<option/>').val(actions.RENAME).text(TYPO3.lang['file_upload.actions.all.rename']), - $('<option/>').val(actions.OVERRIDE).text(TYPO3.lang['file_upload.actions.all.override']) - ) - ) - ); - - $modal.on('change', '.t3js-actions-all', function() { - var $me = $(this), - value = $me.val(); - - if (value !== '') { - // mass action was selected, apply action to every file - $modal.find('.t3js-actions').each(function(i, select) { - var $select = $(select), - index = parseInt($select.data('override')); - $select.val(value).prop('disabled', 'disabled'); - askForOverride[index].action = $select.val(); - }); - } else { - $modal.find('.t3js-actions').removeProp('disabled'); - } - }).on('change', '.t3js-actions', function() { - var $me = $(this), - index = parseInt($me.data('override')); - askForOverride[index].action = $me.val(); - }).on('button.clicked', function(e) { - if (e.target.name === 'cancel') { - askForOverride = []; - Modal.dismiss(); - } else if (e.target.name === 'continue') { - $.each(askForOverride, function(key, fileInfo) { - if (fileInfo.action === actions.USE_EXISTING) { - DragUploader.addFileToIrre( - me.irreObjectUid, - fileInfo.original - ); - } else if (fileInfo.action !== actions.SKIP) { - new FileQueueItem(me, fileInfo.uploaded, fileInfo.action); - } - }); - askForOverride = []; - Modal.dismiss(); - } - }).on('hidden.bs.modal', function() { - askForOverride = []; - }); - } - }; - - var FileQueueItem = function(dragUploader, file, override) { - var me = this; - me.dragUploader = dragUploader; - me.file = file; - me.override = override; - - me.$row = $('<tr />').addClass('upload-queue-item uploading'); - me.$iconCol = $('<td />').addClass('col-icon').appendTo(me.$row); - me.$fileName = $('<td />').text(file.name).appendTo(me.$row); - me.$progress = $('<td />').attr('colspan', me.dragUploader.fileListColumnCount - 2).appendTo(me.$row); - me.$progressContainer = $('<div />').addClass('upload-queue-progress').appendTo(me.$progress); - me.$progressBar = $('<div />').addClass('upload-queue-progress-bar').appendTo(me.$progressContainer); - me.$progressPercentage = $('<span />').addClass('upload-queue-progress-percentage').appendTo(me.$progressContainer); - me.$progressMessage = $('<span />').addClass('upload-queue-progress-message').appendTo(me.$progressContainer); - - me.updateMessage = function(message) { - me.$progressMessage.text(message); - }; - - me.removeProgress = function() { - if (me.$progress) { - me.$progress.remove(); - } - }; - - me.uploadStart = function() { - me.$progressPercentage.text('(0%)'); - me.$progressBar.width('1%'); - me.dragUploader.$trigger.trigger('uploadStart', [me]); - }; - - me.uploadError = function(response) { - me.updateMessage(TYPO3.lang['file_upload.uploadFailed'].replace(/\{0\}/g, me.file.name)); - var error = $(response.responseText); - if (error.is('t3err')) { - me.$progressPercentage.text(error.text()); - } else { - me.$progressPercentage.text('(' + response.statusText + ')'); - } - me.$row.addClass('error'); - me.dragUploader.decrementQueueLength(); - me.dragUploader.$trigger.trigger('uploadError', [me, response]); - }; - - me.updateProgress = function(event) { - var percentage = Math.round((event.loaded / event.total) * 100) + '%'; - me.$progressBar.outerWidth(percentage); - me.$progressPercentage.text(percentage); - me.dragUploader.$trigger.trigger('updateProgress', [me, percentage, event]); - }; - - me.uploadSuccess = function(data) { - if (data.upload) { - me.dragUploader.decrementQueueLength(); - me.$row.removeClass('uploading'); - me.$fileName.text(data.upload[0].name); - me.$progressPercentage.text(''); - me.$progressMessage.text('100%'); - me.$progressBar.outerWidth('100%'); - - // replace file icon - if (data.upload[0].icon) { - me.$iconCol.html('<a href="#" class="t3js-contextmenutrigger" data-uid="' + data.upload[0].id + '" data-table="sys_file">' + data.upload[0].icon + ' </span></a>'); - } - - if (me.dragUploader.irreObjectUid) { - DragUploader.addFileToIrre( - me.dragUploader.irreObjectUid, - data.upload[0] - ); - setTimeout(function() { - me.$row.remove(); - if ($('tr', me.dragUploader.$fileList).length === 0) { - me.dragUploader.$fileList.hide(); - me.dragUploader.$trigger.trigger('uploadSuccess', [me, data]); - } - }, 3000); - } else { - setTimeout(function() { - me.showFileInfo(data.upload[0]); - me.dragUploader.$trigger.trigger('uploadSuccess', [me, data]); - }, 3000); - } - } - }; - - me.showFileInfo = function(fileInfo) { - me.removeProgress(); - // add spacing cells when clibboard and/or extended view is enabled - for (i = 7; i < me.dragUploader.fileListColumnCount; i++) { - $('<td />').text('').appendTo(me.$row); - } - $('<td />').text(fileInfo.extension.toUpperCase()).appendTo(me.$row); - $('<td />').text(fileInfo.date).appendTo(me.$row); - $('<td />').text(DragUploader.fileSizeAsString(fileInfo.size)).appendTo(me.$row); - var permissions = ''; - if (fileInfo.permissions.read) { - permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.read'] + '</strong>'; - } - if (fileInfo.permissions.write) { - permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.write'] + '</strong>'; - } - $('<td />').html(permissions).appendTo(me.$row); - $('<td />').text('-').appendTo(me.$row); - }; - - me.checkAllowedExtensions = function() { - if (!me.dragUploader.filesExtensionsAllowed) { - return true; - } - var extension = me.file.name.split('.').pop(); - var allowed = me.dragUploader.filesExtensionsAllowed.split(','); - if ($.inArray(extension.toLowerCase(), allowed) !== -1) { - return true; - } - return false; - }; - - // position queue item in filelist - if ($('tbody tr.upload-queue-item', me.dragUploader.$fileList).length === 0) { - me.$row.prependTo($('tbody', me.dragUploader.$fileList)); - me.$row.addClass('last'); - } else { - me.$row.insertBefore($('tbody tr.upload-queue-item:first', me.dragUploader.$fileList)); - } - - // set dummy file icon - me.$iconCol.html('<span class="t3-icon t3-icon-mimetypes t3-icon-other-other"> </span>') - - // check file size - if (me.dragUploader.maxFileSize > 0 && me.file.size > me.dragUploader.maxFileSize) { - me.updateMessage(TYPO3.lang['file_upload.maxFileSizeExceeded'] - .replace(/\{0\}/g, me.file.name) - .replace(/\{1\}/g, DragUploader.fileSizeAsString(me.dragUploader.maxFileSize))); - me.$row.addClass('error'); - - // check filename/extension against deny pattern - } else if (me.dragUploader.fileDenyPattern && me.file.name.match(me.dragUploader.fileDenyPattern)) { - me.updateMessage(TYPO3.lang['file_upload.fileNotAllowed'].replace(/\{0\}/g, me.file.name)); - me.$row.addClass('error'); - - } else if (!me.checkAllowedExtensions()) { - me.updateMessage(TYPO3.lang['file_upload.fileExtensionExpected'] - .replace(/\{0\}/g, me.dragUploader.filesExtensionsAllowed) - ); - me.$row.addClass('error'); - } else { - me.updateMessage('- ' + DragUploader.fileSizeAsString(me.file.size)); - - var formData = new FormData(); - formData.append('data[upload][1][target]', me.dragUploader.target); - formData.append('data[upload][1][data]', '1'); - formData.append('overwriteExistingFiles', me.override); - formData.append('redirect', ''); - formData.append('upload_1', me.file); - - var s = $.extend(true, {}, $.ajaxSettings, { - url: TYPO3.settings.ajaxUrls['file_process'], - contentType: false, - processData: false, - data: formData, - cache: false, - type: 'POST', - success: me.uploadSuccess, - error: me.uploadError - }); - - s.xhr = function() { - var xhr = $.ajaxSettings.xhr(); - xhr.upload.addEventListener('progress', me.updateProgress); - return xhr; - }; - - // start upload - me.upload = $.ajax(s); - } - }; - - /** - * part 2: The main module of this file - * - initialize the DragUploader module and register - * the jQuery plugin in the jQuery global object - * when initializing the DragUploader module - */ - var DragUploader = {}; - - DragUploader.options = {}; - - DragUploader.fileSizeAsString = function(size) { - var string = '', - sizeKB = size / 1024; - - if (parseInt(sizeKB) > 1024) { - var sizeMB = sizeKB / 1024; - string = sizeMB.toFixed(1) + ' MB'; - } else { - string = sizeKB.toFixed(1) + ' KB'; - } - return string; - }; - - DragUploader.addFileToIrre = function(irre_object, file) { - inline.delayedImportElement( - irre_object, - 'sys_file', - file.uid, - 'file' - ); - }; - - DragUploader.initialize = function() { - var me = this, - opts = me.options; - - // register the jQuery plugin "DragUploaderPlugin" - $.fn.dragUploader = function(option) { - return this.each(function() { - var $this = $(this), - data = $this.data('DragUploaderPlugin'); - if (!data) { - $this.data('DragUploaderPlugin', (data = new DragUploaderPlugin(this))); - } - if (typeof option === 'string') { - data[option](); - } - }); - }; - - $(function() { - $('.t3js-drag-uploader').dragUploader(); - }); - }; - - - /** - * part 3: initialize the RequireJS module, require possible post-initialize hooks, - * and return the main object - */ - var initialize = function() { - - DragUploader.initialize(); - - // load required modules to hook in the post initialize function - if ( - 'undefined' !== typeof TYPO3.settings - && 'undefined' !== typeof TYPO3.settings.RequireJS - && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules - && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'] - ) { - $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], function(pos, moduleName) { - require([moduleName]); - }); - } - - // return the object in the global space - return DragUploader; - }; - - // call the main initialize function and execute the hooks - return initialize(); - -}); +define(["require","exports","jquery","moment","nprogress","TYPO3/CMS/Backend/Modal","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Backend/Severity"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(b,"__esModule",{value:!0});var i;!function(a){a.OVERRIDE="replace",a.RENAME="rename",a.SKIP="cancel",a.USE_EXISTING="useExisting"}(i||(i={}));var j=function(){function a(a){var b=this;this.askForOverride=[],this.percentagePerFile=1,this.dragFileIntoDocument=function(a){return a.stopPropagation(),a.preventDefault(),c(a.currentTarget).addClass("drop-in-progress"),b.showDropzone(),!1},this.dragAborted=function(a){return a.stopPropagation(),a.preventDefault(),c(a.currentTarget).removeClass("drop-in-progress"),!1},this.ignoreDrop=function(a){return a.stopPropagation(),a.preventDefault(),b.dragAborted(a),!1},this.handleDrop=function(a){b.ignoreDrop(a),b.processFiles(a.originalEvent.dataTransfer.files),b.$dropzone.removeClass("drop-status-ok")},this.fileInDropzone=function(){b.$dropzone.addClass("drop-status-ok")},this.fileOutOfDropzone=function(){b.$dropzone.removeClass("drop-status-ok")},this.$body=c("body"),this.$element=c(a);var d=void 0!==this.$element.data("dropzoneTrigger");this.$trigger=c(this.$element.data("dropzoneTrigger")),this.$dropzone=c("<div />").addClass("dropzone").hide(),this.irreObjectUid=this.$element.data("fileIrreObject");var e=this.$element.data("dropzoneTarget");return this.irreObjectUid&&0!==this.$element.nextAll(e).length?(this.dropZoneInsertBefore=!0,this.$dropzone.insertBefore(e)):(this.dropZoneInsertBefore=!1,this.$dropzone.insertAfter(e)),this.$dropzoneMask=c("<div />").addClass("dropzone-mask").appendTo(this.$dropzone),this.fileInput=document.createElement("input"),this.fileInput.setAttribute("type","file"),this.fileInput.setAttribute("multiple","multiple"),this.fileInput.setAttribute("name","files[]"),this.fileInput.classList.add("upload-file-picker"),this.$body.append(this.fileInput),this.$fileList=c(this.$element.data("progress-container")),this.fileListColumnCount=c("thead tr:first th",this.$fileList).length,this.filesExtensionsAllowed=this.$element.data("file-allowed"),this.fileDenyPattern=this.$element.data("file-deny-pattern")?new RegExp(this.$element.data("file-deny-pattern"),"i"):null,this.maxFileSize=parseInt(this.$element.data("max-file-size"),10),this.target=this.$element.data("target-folder"),this.browserCapabilities={fileReader:"undefined"!=typeof FileReader,DnD:"draggable"in document.createElement("span"),Progress:"upload"in new XMLHttpRequest},this.browserCapabilities.DnD?(this.$body.on("dragover",this.dragFileIntoDocument),this.$body.on("dragend",this.dragAborted),this.$body.on("drop",this.ignoreDrop),this.$dropzone.on("dragenter",this.fileInDropzone),this.$dropzoneMask.on("dragenter",this.fileInDropzone),this.$dropzoneMask.on("dragleave",this.fileOutOfDropzone),this.$dropzoneMask.on("drop",function(a){return b.handleDrop(a)}),this.$dropzone.prepend('<div class="dropzone-hint"><div class="dropzone-hint-media"><div class="dropzone-hint-icon"></div></div><div class="dropzone-hint-body"><h3 class="dropzone-hint-title">'+TYPO3.lang["file_upload.dropzonehint.title"]+'</h3><p class="dropzone-hint-message">'+TYPO3.lang["file_upload.dropzonehint.message"]+"</p></div></div>").click(function(){b.fileInput.click()}),c("<span />").addClass("dropzone-close").click(this.hideDropzone).appendTo(this.$dropzone),0===this.$fileList.length&&(this.$fileList=c("<table />").attr("id","typo3-filelist").addClass("table table-striped table-hover upload-queue").html("<tbody></tbody>").hide(),this.dropZoneInsertBefore?this.$fileList.insertAfter(this.$dropzone):this.$fileList.insertBefore(this.$dropzone),this.fileListColumnCount=7),this.fileInput.addEventListener("change",function(){b.processFiles(Array.apply(null,b.fileInput.files))}),void this.bindUploadButton(d===!0?this.$trigger:this.$element)):void console.warn("Browser has no Drag and drop capabilities; cannot initialize DragUploader")}return a.prototype.showDropzone=function(){this.$dropzone.show()},a.prototype.hideDropzone=function(a){a.stopPropagation(),a.preventDefault(),this.$dropzone.hide()},a.prototype.processFiles=function(a){var b=this;this.queueLength=a.length,this.$fileList.is(":visible")||this.$fileList.show(),e.start(),this.percentagePerFile=1/a.length;var d=[];c.each(a,function(a,f){d[parseInt(a,10)]=c.ajax({url:TYPO3.settings.ajaxUrls.file_exists,data:{fileName:f.name,fileTarget:b.target},cache:!1,success:function(a){var c="undefined"!=typeof a.uid;if(c)b.askForOverride.push({original:a,uploaded:f,action:b.irreObjectUid?i.USE_EXISTING:i.SKIP}),e.inc(b.percentagePerFile);else{new k(b,f,i.SKIP)}}})}),c.when.apply(c,d).done(function(){b.drawOverrideModal(),e.done()}),this.fileInput.value=""},a.prototype.bindUploadButton=function(a){var b=this;a.click(function(a){a.preventDefault(),b.fileInput.click(),b.showDropzone()})},a.prototype.decrementQueueLength=function(){this.queueLength>0&&(this.queueLength--,0===this.queueLength&&c.ajax({url:TYPO3.settings.ajaxUrls.flashmessages_render,cache:!1,success:function(a){c.each(a,function(a,b){g.showMessage(b.title,b.message,b.severity)})}}))},a.prototype.drawOverrideModal=function(){var a=this,b=Object.keys(this.askForOverride).length;if(0!==b){for(var e=c("<div/>").append(c("<p/>").text(TYPO3.lang["file_upload.existingfiles.description"]),c("<table/>",{class:"table"}).append(c("<thead/>").append(c("<tr />").append(c("<th/>"),c("<th/>").text(TYPO3.lang["file_upload.header.originalFile"]),c("<th/>").text(TYPO3.lang["file_upload.header.uploadedFile"]),c("<th/>").text(TYPO3.lang["file_upload.header.action"]))))),g=0;g<b;++g){var j=c("<tr />").append(c("<td />").append(""!==this.askForOverride[g].original.thumbUrl?c("<img />",{src:this.askForOverride[g].original.thumbUrl,height:40}):c(this.askForOverride[g].original.icon)),c("<td />").html(this.askForOverride[g].uploaded.name+" ("+l.fileSizeAsString(this.askForOverride[g].uploaded.size)+")<br>"+d(this.askForOverride[g].uploaded.lastModifiedDate,"x").format("YYYY-MM-DD HH:mm")),c("<td />").html(this.askForOverride[g].uploaded.name+" ("+l.fileSizeAsString(this.askForOverride[g].original.size)+")<br>"+d(this.askForOverride[g].original.mtime,"X").format("YYYY-MM-DD HH:mm")),c("<td />").append(c("<select />",{class:"form-control t3js-actions","data-override":g}).append(this.irreObjectUid?c("<option/>").val(i.USE_EXISTING).text(TYPO3.lang["file_upload.actions.use_existing"]):"",c("<option />").val(i.SKIP).text(TYPO3.lang["file_upload.actions.skip"]),c("<option />").val(i.RENAME).text(TYPO3.lang["file_upload.actions.rename"]),c("<option />").val(i.OVERRIDE).text(TYPO3.lang["file_upload.actions.override"]))));e.find("table").append("<tbody />").append(j)}var m=f.confirm(TYPO3.lang["file_upload.existingfiles.title"],e,h.warning,[{text:c(this).data("button-close-text")||TYPO3.lang["file_upload.button.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:c(this).data("button-ok-text")||TYPO3.lang["file_upload.button.continue"]||"Continue with selected actions",btnClass:"btn-warning",name:"continue"}],["modal-inner-scroll"]);m.find(".modal-dialog").addClass("modal-lg"),m.find(".modal-footer").prepend(c("<span/>").addClass("form-inline").append(c("<label/>").text(TYPO3.lang["file_upload.actions.all.label"]),c("<select/>",{class:"form-control t3js-actions-all"}).append(c("<option/>").val("").text(TYPO3.lang["file_upload.actions.all.empty"]),this.irreObjectUid?c("<option/>").val(i.USE_EXISTING).text(TYPO3.lang["file_upload.actions.all.use_existing"]):"",c("<option/>").val(i.SKIP).text(TYPO3.lang["file_upload.actions.all.skip"]),c("<option/>").val(i.RENAME).text(TYPO3.lang["file_upload.actions.all.rename"]),c("<option/>").val(i.OVERRIDE).text(TYPO3.lang["file_upload.actions.all.override"]))));var n=this;m.on("change",".t3js-actions-all",function(){var a=c(this),b=a.val();""!==b?m.find(".t3js-actions").each(function(a,d){var e=c(d),f=parseInt(e.data("override"),10);e.val(b).prop("disabled","disabled"),n.askForOverride[f].action=e.val()}):m.find(".t3js-actions").removeProp("disabled")}).on("change",".t3js-actions",function(){var a=c(this),b=parseInt(a.data("override"),10);n.askForOverride[b].action=a.val()}).on("button.clicked",function(a){"cancel"===a.target.name?(n.askForOverride=[],f.dismiss()):"continue"===a.target.name&&(c.each(n.askForOverride,function(a,b){if(b.action===i.USE_EXISTING)l.addFileToIrre(n.irreObjectUid,b.original);else if(b.action!==i.SKIP){new k(n,b.uploaded,b.action)}}),n.askForOverride=[],f.dismiss())}).on("hidden.bs.modal",function(){a.askForOverride=[]})}},a}(),k=function(){function a(a,b,d){var e=this;if(this.dragUploader=a,this.file=b,this.override=d,this.$row=c("<tr />").addClass("upload-queue-item uploading"),this.$iconCol=c("<td />").addClass("col-icon").appendTo(this.$row),this.$fileName=c("<td />").text(b.name).appendTo(this.$row),this.$progress=c("<td />").attr("colspan",this.dragUploader.fileListColumnCount-2).appendTo(this.$row),this.$progressContainer=c("<div />").addClass("upload-queue-progress").appendTo(this.$progress),this.$progressBar=c("<div />").addClass("upload-queue-progress-bar").appendTo(this.$progressContainer),this.$progressPercentage=c("<span />").addClass("upload-queue-progress-percentage").appendTo(this.$progressContainer),this.$progressMessage=c("<span />").addClass("upload-queue-progress-message").appendTo(this.$progressContainer),0===c("tbody tr.upload-queue-item",this.dragUploader.$fileList).length?(this.$row.prependTo(c("tbody",this.dragUploader.$fileList)),this.$row.addClass("last")):this.$row.insertBefore(c("tbody tr.upload-queue-item:first",this.dragUploader.$fileList)),this.$iconCol.html('<span class="t3-icon t3-icon-mimetypes t3-icon-other-other"> </span>'),this.dragUploader.maxFileSize>0&&this.file.size>this.dragUploader.maxFileSize)this.updateMessage(TYPO3.lang["file_upload.maxFileSizeExceeded"].replace(/\{0\}/g,this.file.name).replace(/\{1\}/g,l.fileSizeAsString(this.dragUploader.maxFileSize))),this.$row.addClass("error");else if(this.dragUploader.fileDenyPattern&&this.file.name.match(this.dragUploader.fileDenyPattern))this.updateMessage(TYPO3.lang["file_upload.fileNotAllowed"].replace(/\{0\}/g,this.file.name)),this.$row.addClass("error");else if(this.checkAllowedExtensions()){this.updateMessage("- "+l.fileSizeAsString(this.file.size));var f=new FormData;f.append("data[upload][1][target]",this.dragUploader.target),f.append("data[upload][1][data]","1"),f.append("overwriteExistingFiles",this.override),f.append("redirect",""),f.append("upload_1",this.file);var g=c.extend(!0,{},c.ajaxSettings,{url:TYPO3.settings.ajaxUrls.file_process,contentType:!1,processData:!1,data:f,cache:!1,type:"POST",success:function(a){return e.uploadSuccess(a)},error:function(a){return e.uploadError(a)}});g.xhr=function(){var a=c.ajaxSettings.xhr();return a.upload.addEventListener("progress",function(a){return e.updateProgress(a)}),a},this.upload=c.ajax(g)}else this.updateMessage(TYPO3.lang["file_upload.fileExtensionExpected"].replace(/\{0\}/g,this.dragUploader.filesExtensionsAllowed)),this.$row.addClass("error")}return a.prototype.updateMessage=function(a){this.$progressMessage.text(a)},a.prototype.removeProgress=function(){this.$progress&&this.$progress.remove()},a.prototype.uploadStart=function(){this.$progressPercentage.text("(0%)"),this.$progressBar.width("1%"),this.dragUploader.$trigger.trigger("uploadStart",[this])},a.prototype.uploadError=function(a){this.updateMessage(TYPO3.lang["file_upload.uploadFailed"].replace(/\{0\}/g,this.file.name));var b=c(a.responseText);b.is("t3err")?this.$progressPercentage.text(b.text()):this.$progressPercentage.text("("+a.statusText+")"),this.$row.addClass("error"),this.dragUploader.decrementQueueLength(),this.dragUploader.$trigger.trigger("uploadError",[this,a])},a.prototype.updateProgress=function(a){var b=Math.round(a.loaded/a.total*100)+"%";this.$progressBar.outerWidth(b),this.$progressPercentage.text(b),this.dragUploader.$trigger.trigger("updateProgress",[this,b,a])},a.prototype.uploadSuccess=function(a){var b=this;a.upload&&(this.dragUploader.decrementQueueLength(),this.$row.removeClass("uploading"),this.$fileName.text(a.upload[0].name),this.$progressPercentage.text(""),this.$progressMessage.text("100%"),this.$progressBar.outerWidth("100%"),a.upload[0].icon&&this.$iconCol.html('<a href="#" class="t3js-contextmenutrigger" data-uid="'+a.upload[0].id+'" data-table="sys_file">'+a.upload[0].icon+" </span></a>"),this.dragUploader.irreObjectUid?(l.addFileToIrre(this.dragUploader.irreObjectUid,a.upload[0]),setTimeout(function(){b.$row.remove(),0===c("tr",b.dragUploader.$fileList).length&&(b.dragUploader.$fileList.hide(),b.dragUploader.$trigger.trigger("uploadSuccess",[b,a]))},3e3)):setTimeout(function(){b.showFileInfo(a.upload[0]),b.dragUploader.$trigger.trigger("uploadSuccess",[b,a])},3e3))},a.prototype.showFileInfo=function(a){this.removeProgress();for(var b=7;b<this.dragUploader.fileListColumnCount;b++)c("<td />").text("").appendTo(this.$row);c("<td />").text(a.extension.toUpperCase()).appendTo(this.$row),c("<td />").text(a.date).appendTo(this.$row),c("<td />").text(l.fileSizeAsString(a.size)).appendTo(this.$row);var d="";a.permissions.read&&(d+='<strong class="text-danger">'+TYPO3.lang["permissions.read"]+"</strong>"),a.permissions.write&&(d+='<strong class="text-danger">'+TYPO3.lang["permissions.write"]+"</strong>"),c("<td />").html(d).appendTo(this.$row),c("<td />").text("-").appendTo(this.$row)},a.prototype.checkAllowedExtensions=function(){if(!this.dragUploader.filesExtensionsAllowed)return!0;var a=this.file.name.split(".").pop(),b=this.dragUploader.filesExtensionsAllowed.split(",");return c.inArray(a.toLowerCase(),b)!==-1},a}(),l=function(){function a(){}return a.fileSizeAsString=function(a){var b=a/1024,c="";return c=b>1024?(b/1024).toFixed(1)+" MB":b.toFixed(1)+" KB"},a.addFileToIrre=function(a,b){window.inline.delayedImportElement(a,"sys_file",b.uid,"file")},a.init=function(){var a=this,b=a.options;c.fn.extend({dragUploader:function(a){return this.each(function(b,d){var e=c(d),f=e.data("DragUploaderPlugin");f||e.data("DragUploaderPlugin",f=new j(d)),"string"==typeof a&&f[a]()})}}),c(function(){c(".t3js-drag-uploader").dragUploader(b)})},a}();b.initialize=function(){l.init(),"undefined"!=typeof TYPO3.settings&&"undefined"!=typeof TYPO3.settings.RequireJS&&"undefined"!=typeof TYPO3.settings.RequireJS.PostInitializationModules&&"undefined"!=typeof TYPO3.settings.RequireJS.PostInitializationModules["TYPO3/CMS/Backend/DragUploader"]&&c.each(TYPO3.settings.RequireJS.PostInitializationModules["TYPO3/CMS/Backend/DragUploader"],function(b,c){a([c])})},b.initialize()}); \ No newline at end of file