Files

56 lines
20 KiB
JavaScript

/*
* 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!
*/
import L from"@typo3/core/document-service.js";import{SeverityEnum as c}from"@typo3/backend/enum/severity.js";import{MessageUtility as w}from"@typo3/backend/utility/message-utility.js";import S from"@typo3/core/ajax/ajax-request.js";import b,{Sizes as z}from"@typo3/backend/modal.js";import m from"@typo3/backend/notification.js";import A from"@typo3/backend/action-button/immediate-action.js";import x from"@typo3/backend/hashing/md5.js";import"@typo3/backend/element/icon-element.js";import E from"@typo3/core/event/regular-event.js";import k from"@typo3/backend/utility/dom-helper.js";import{KeyTypesEnum as D}from"@typo3/backend/enum/key-types.js";import"@typo3/backend/element/progress-bar-element.js";import{topLevelModuleImport as I}from"@typo3/backend/utility/top-level-module-import.js";import{FormatUtility as u}from"@typo3/backend/utility/format-utility.js";import r from"~labels/core.core";import U from"~labels/core.common";import v from"~labels/filelist.mod_file_list";var n;(function(h){h.OVERRIDE="replace",h.RENAME="rename",h.SKIP="cancel",h.USE_EXISTING="useExisting"})(n||(n={}));var d;(function(h){h.MANAGE="manage",h.BROWSE="browse"})(d||(d={}));const g="typo3:drag-uploader:upload-finished";class p{constructor(t){this.askForOverride=[],this.percentagePerFile=1,this.overallProgressBar=null,this.dragStartedInDocument=!1,this.closeDropzone=()=>{this.dropzone.hidden=!0,this.dropzone.classList.remove("drop-status-ok"),this.irreObjectUid!==void 0&&(this.fileList.parentElement.hidden=!0),this.manuallyTriggered=!1},this.hideDropzone=e=>{e.stopPropagation(),e.preventDefault(),this.closeDropzone()},this.dragFileIntoDocument=e=>(this.dragStartedInDocument||!e.dataTransfer.types.includes("Files")||(e.stopPropagation(),e.preventDefault(),e.currentTarget.classList.add("drop-in-progress"),this.element.offsetParent&&this.showDropzone()),!1),this.dragAborted=e=>(e.stopPropagation(),e.preventDefault(),e.currentTarget.classList.remove("drop-in-progress"),this.dragStartedInDocument=!1,!1),this.ignoreDrop=e=>(e.stopPropagation(),e.preventDefault(),this.dragAborted(e),!1),this.handleDrop=e=>{this.ignoreDrop(e),this.hideDropzone(e),this.processFiles(e.dataTransfer.files)},this.fileInDropzone=()=>{this.dropzone.classList.add("drop-status-ok")},this.fileOutOfDropzone=()=>{this.dropzone.classList.remove("drop-status-ok"),this.manuallyTriggered||this.dropzone.setAttribute("hidden","hidden")},this.body=document.querySelector("body"),this.element=t,this.trigger=document.querySelector(this.element.dataset.dropzoneTrigger),this.defaultAction=this.element.dataset.defaultAction||n.SKIP,this.dropzone=document.createElement("div"),this.dropzone.classList.add("dropzone"),this.dropzone.setAttribute("hidden","hidden"),this.irreObjectUid=this.element.dataset.fileIrreObject,this.manualTable=this.element.hasAttribute("data-manual-table");const s=document.querySelector(this.element.dataset.dropzoneTarget);if(this.irreObjectUid&&k.nextAll(s).length!==0?(this.dropZoneInsertBefore=!0,s.before(this.dropzone)):(this.dropZoneInsertBefore=!1,s.after(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=document.querySelector(this.element.dataset.progressContainer),this.fileListColumnCount=this.fileList?.querySelectorAll("thead tr:first-child th").length+1,this.filesExtensionsAllowed=this.element.dataset.fileAllowed,this.filesExtensionsDisallowed=this.element.dataset.fileDisallowed,this.fileDenyPattern=this.element.dataset.fileDenyPattern?new RegExp(this.element.dataset.fileDenyPattern,"i"):null,this.maxFileSize=parseInt(this.element.dataset.maxFileSize,10),this.target=this.element.dataset.targetFolder,this.reloadUrl=this.element.dataset.reloadUrl,this.browserCapabilities={fileReader:typeof FileReader<"u",DnD:"draggable"in document.createElement("span"),Progress:"upload"in new XMLHttpRequest},!this.browserCapabilities.DnD){console.warn("Browser has no Drag and drop capabilities; cannot initialize DragUploader");return}this.body.addEventListener("dragstart",()=>{this.dragStartedInDocument=!0}),this.body.addEventListener("dragover",this.dragFileIntoDocument),this.body.addEventListener("dragend",this.dragAborted),this.body.addEventListener("drop",this.ignoreDrop),this.dropzone.innerHTML='<button type="button" class="dropzone-hint" aria-labelledby="dropzone-title"><div class="dropzone-hint-media"><div class="dropzone-hint-icon"></div></div><div class="dropzone-hint-body"><h3 id="dropzone-title" class="dropzone-hint-title">'+r.get("file_upload.dropzonehint.title")+'</h3><p class="dropzone-hint-message">'+r.get("file_upload.dropzonehint.message",{u:e=>"<u>"+e.join("")+"</u>"})+"</p></div></div>",this.dropzoneMask=document.createElement("div"),this.dropzoneMask.classList.add("dropzone-mask"),this.dropzone.append(this.dropzoneMask),this.dropzone.addEventListener("dragenter",this.fileInDropzone),this.dropzoneMask.addEventListener("dragenter",this.fileInDropzone),this.dropzoneMask.addEventListener("dragleave",this.fileOutOfDropzone),this.dropzoneMask.addEventListener("drop",e=>this.handleDrop(e)),this.dropzone.addEventListener("click",()=>{this.fileInput.click()});const o=document.createElement("button");if(o.classList.add("dropzone-close"),o.type="button",o.setAttribute("aria-label",r.get("file_upload.dropzone.close")),o.addEventListener("click",this.hideDropzone),this.dropzone.append(o),document.addEventListener(g,this.closeDropzone),this.fileList===null){this.fileList=document.createElement("table"),this.fileList.setAttribute("id","typo3-filelist"),this.fileList.classList.add("table","table-striped","table-hover","upload-queue"),this.fileList.innerHTML="<tbody></tbody>";const e=document.createElement("div");e.classList.add("table-fit"),e.setAttribute("hidden","hidden"),e.append(this.fileList),this.dropZoneInsertBefore?this.dropzone.after(e):this.dropzone.before(e),this.fileListColumnCount=8,this.manualTable=!0}this.fileInput.addEventListener("change",e=>{this.hideDropzone(e),this.processFiles(this.fileInput.files)}),document.addEventListener("keydown",e=>{e.key===D.ENTER&&!this.dropzone.hasAttribute("hidden")&&this.hideDropzone(e)}),this.bindUploadButton(this.trigger!==null?this.trigger:this.element)}static init(){new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const e of[...o.addedNodes.values()])e instanceof HTMLElement&&(e.matches(".t3js-drag-uploader")?new p(e):e.querySelectorAll(".t3js-drag-uploader").forEach(i=>{new p(i)}))}).observe(document,{childList:!0,subtree:!0}),L.ready().then(()=>{document.querySelectorAll(".t3js-drag-uploader").forEach(s=>{new p(s)})})}static addFileToIrre(t,s){const o={actionName:"typo3:foreignRelation:insert",objectGroup:t,table:"sys_file",uid:s.uid};w.send(o)}showDropzone(){this.dropzone.removeAttribute("hidden")}processFiles(t){this.queueLength=t.length,this.fileList.parentElement.hasAttribute("hidden")&&(this.fileList.parentElement.removeAttribute("hidden"),this.fileList.closest(".t3-filelist-container")?.classList.remove("hidden"),this.fileList.closest(".filelist-main")?.querySelector(".t3-filelist-info-container")?.setAttribute("hidden","hidden")),this.overallProgressBar=document.createElement("typo3-backend-progress-bar"),document.body.appendChild(this.overallProgressBar),this.overallProgressBar.start(),this.percentagePerFile=1/t.length;const s=[];Array.from(t).forEach(o=>{const e=new S(TYPO3.settings.ajaxUrls.file_exists).withQueryArguments({fileName:o.name,fileTarget:this.target}).get({cache:"no-cache"}).then(async i=>{const a=await i.resolve();typeof a.uid<"u"?(this.askForOverride.push({original:a,uploaded:o,action:this.irreObjectUid?n.USE_EXISTING:this.defaultAction}),this.overallProgressBar&&this.overallProgressBar.inc(this.percentagePerFile)):new y(this,o,n.SKIP,this.fileList.dataset.mode===d.BROWSE?d.BROWSE:d.MANAGE)});s.push(e)}),Promise.all(s).then(async()=>{await this.drawOverrideModal(),this.overallProgressBar&&this.overallProgressBar.done()}),this.fileInput.value=""}bindUploadButton(t){t.addEventListener("click",s=>{s.preventDefault(),this.fileInput.click(),this.manuallyTriggered=!0})}decrementQueueLength(t){if(this.queueLength>0&&(this.queueLength--,this.queueLength===0)){document.dispatchEvent(new CustomEvent(g));const s=t&&t.length?5e3:0;if(s)for(const o of t)m.showMessage(o.title,o.message,o.severity);this.reloadUrl&&setTimeout(()=>{m.info(r.get("file_upload.reload.filelist"),r.get("file_upload.reload.filelist.message"),10,[{label:r.get("file_upload.reload.filelist.actions.dismiss")},{label:r.get("file_upload.reload.filelist.actions.reload"),action:new A(()=>{top.list_frame.document.location.href=this.reloadUrl})}])},s)}}async drawOverrideModal(){const t=Object.keys(this.askForOverride).length;if(t===0)return;await I("@typo3/backend/element/datetime-element.js");const s=document.createElement("div");let o=`
<p>${r.get("file_upload.existingfiles.description")}</p>
<table class="table">
<thead>
<tr>
<th></th>
<th>${r.get("file_upload.header.originalFile")}</th>
<th>${r.get("file_upload.header.uploadedFile")}</th>
<th>${r.get("file_upload.header.action")}</th>
</tr>
</thead>
<tbody>
`;for(let i=0;i<t;++i){const a=`
<tr>
<td>
${this.askForOverride[i].original.thumbUrl!==""?`<img src="${this.askForOverride[i].original.thumbUrl}" height="40" />`:this.askForOverride[i].original.icon}
</td>
<td>
${this.askForOverride[i].original.name} (${u.fileSizeAsString(this.askForOverride[i].original.size)})<br />
<typo3-backend-datetime datetime="${this.askForOverride[i].original.mtime}" format="datetime"></typo3-backend-datetime>
</td>
<td>
${this.askForOverride[i].uploaded.name} (${u.fileSizeAsString(this.askForOverride[i].uploaded.size)})<br />
<typo3-backend-datetime datetime="${Math.floor(this.askForOverride[i].uploaded.lastModified/1e3)}" format="datetime"></typo3-backend-datetime>
</td>
<td>
<select class="form-select t3js-actions" data-override="${i}">
${this.irreObjectUid?`<option value="${n.USE_EXISTING}">${r.get("file_upload.actions.use_existing")}</option>`:""}
<option value="${n.SKIP}" ${this.defaultAction===n.SKIP?"selected":""}>${r.get("file_upload.actions.skip")}</option>
<option value="${n.RENAME}" ${this.defaultAction===n.RENAME?"selected":""}>${r.get("file_upload.actions.rename")}</option>
<option value="${n.OVERRIDE}" ${this.defaultAction===n.OVERRIDE?"selected":""}>${r.get("file_upload.actions.override")}</option>
</select>
</td>
</tr>
`;o+=a}o+="</tbody></table>",s.innerHTML=o;const e=b.advanced({title:r.get("file_upload.existingfiles.title"),content:s,severity:c.warning,buttons:[{text:r.get("file_upload.button.cancel")||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:r.get("file_upload.button.continue")||"Continue with selected actions",btnClass:"btn-warning",name:"continue"}],additionalCssClasses:["modal-inner-scroll"],size:z.large,callback:i=>{const a=i.querySelector(".modal-footer"),l=document.createElement("label");l.textContent=r.get("file_upload.actions.all.label");const f=document.createElement("span");f.innerHTML=`
<select class="form-select t3js-actions-all">
<option value="" selected>${r.get("file_upload.actions.all.empty")}</option>
${this.irreObjectUid?`<option value="${n.USE_EXISTING}">${r.get("file_upload.actions.all.use_existing")}</option>`:""}
<option value="${n.SKIP}">${r.get("file_upload.actions.all.skip")}</option>
<option value="${n.RENAME}">${r.get("file_upload.actions.all.rename")}</option>
<option value="${n.OVERRIDE}">${r.get("file_upload.actions.all.override")}</option>
</select>
`,a.prepend(l,f)}});new E("change",(i,a)=>{if(a.value!=="")for(const l of e.querySelectorAll(".t3js-actions")){const f=parseInt(l.dataset.override,10);l.value=a.value,l.disabled=!0,this.askForOverride[f].action=l.value}else e.querySelectorAll(".t3js-actions").forEach(l=>l.disabled=!1)}).delegateTo(e,".t3js-actions-all"),new E("change",i=>{const a=i.target,l=parseInt(a.dataset.override,10);this.askForOverride[l].action=a.value}).delegateTo(e,".t3js-actions"),e.addEventListener("button.clicked",i=>{const a=i.target;if(a.name==="cancel")this.askForOverride=[],b.dismiss();else if(a.name==="continue"){for(const l of this.askForOverride)l.action===n.USE_EXISTING?p.addFileToIrre(this.irreObjectUid,l.original):l.action!==n.SKIP&&new y(this,l.uploaded,l.action,this.fileList.dataset.mode===d.BROWSE?d.BROWSE:d.MANAGE);this.askForOverride=[],e.hideModal()}}),e.addEventListener("typo3-modal-hidden",()=>{this.askForOverride=[],document.dispatchEvent(new CustomEvent(g))})}}class y{constructor(t,s,o,e){if(this.dragUploader=t,this.file=s,this.override=o,this.mode=e,this.row=document.createElement("tr"),this.row.classList.add("upload-queue-item"),this.dragUploader.manualTable||(this.selector=document.createElement("td"),this.selector.classList.add("col-checkbox"),this.row.append(this.selector)),this.iconCol=document.createElement("td"),this.iconCol.classList.add("col-icon"),this.row.append(this.iconCol),this.fileName=document.createElement("td"),this.fileName.classList.add("col-title","col-responsive"),this.fileName.textContent=s.name,this.row.append(this.fileName),this.progress=document.createElement("td"),this.progress.classList.add("col-progress"),this.progress.setAttribute("colspan",String(this.dragUploader.fileListColumnCount-this.row.querySelectorAll("td").length)),this.row.append(this.progress),this.progressBar=document.createElement("typo3-backend-progress-bar"),this.progress.append(this.progressBar),this.dragUploader.fileList.querySelectorAll("tbody tr.upload-queue-item").length===0?(this.dragUploader.fileList.querySelector("tbody").prepend(this.row),this.row.classList.add("last")):this.dragUploader.fileList.querySelector("tbody tr.upload-queue-item:first-child").before(this.row),this.selector&&(this.selector.innerHTML='<span class="form-check form-check-type-toggle"><input type="checkbox" class="form-check-input t3js-multi-record-selection-check" disabled/></span>'),this.iconCol.innerHTML='<typo3-backend-icon identifier="mimetypes-other-other" />',this.dragUploader.maxFileSize>0&&this.file.size>this.dragUploader.maxFileSize)this.updateMessage(r.get("file_upload.maxFileSizeExceeded",{0:this.file.name,1:u.fileSizeAsString(this.dragUploader.maxFileSize)})),this.progressBar.value=100,this.progressBar.severity=c.error;else if(this.dragUploader.fileDenyPattern&&this.file.name.match(this.dragUploader.fileDenyPattern))this.updateMessage(r.get("file_upload.fileNotAllowed",{0:this.file.name})),this.progressBar.value=100,this.progressBar.severity=c.error;else if(this.isProhibitedByAllowedExtensionsList())this.updateMessage(r.get("file_upload.fileExtensionExpected",{0:this.dragUploader.filesExtensionsAllowed})),this.progressBar.value=100,this.progressBar.severity=c.error;else if(this.isProhibitedByDisallowedExtensionsList())this.updateMessage(r.get("file_upload.fileExtensionDisallowed",{0:this.dragUploader.filesExtensionsDisallowed})),this.progressBar.value=100,this.progressBar.severity=c.error;else{this.updateMessage("- "+u.fileSizeAsString(this.file.size));const i=new FormData;i.append("data[upload][1][target]",this.dragUploader.target),i.append("data[upload][1][data]","1"),i.append("overwriteExistingFiles",this.override),i.append("redirect",""),i.append("upload_1",this.file);const a=new XMLHttpRequest;a.onreadystatechange=()=>{if(a.readyState===XMLHttpRequest.DONE)if(a.status===200)try{const l=JSON.parse(a.responseText);l.hasErrors?this.uploadError(a):this.uploadSuccess(l)}catch{this.uploadError(a)}else this.uploadError(a)},a.upload.addEventListener("progress",l=>this.updateProgress(l)),a.open("POST",TYPO3.settings.ajaxUrls.file_process),a.send(i)}}updateMessage(t){this.progressBar.label=t}removeProgress(){this.progress&&this.progress.remove()}uploadError(t){const s=r.get("file_upload.uploadFailed",{0:this.file.name});this.updateMessage(s);try{const e=JSON.parse(t.responseText).messages;if(e&&e.length)for(const i of e)m.showMessage(i.title,i.message,i.severity,10)}catch{}this.progressBar.severity=c.error,this.dragUploader.decrementQueueLength(),this.dragUploader.trigger?.dispatchEvent(new CustomEvent("uploadError",{detail:[this,t]}))}updateProgress(t){const s=Math.round(t.loaded/t.total*100);this.progressBar.value=s,this.progressBar.label=`${r.get("file_upload.upload-in-progress")} ${s}%`,this.dragUploader.trigger?.dispatchEvent(new CustomEvent("updateProgress",{detail:[this,s,t]}))}uploadSuccess(t){if(t.upload){this.dragUploader.decrementQueueLength(t.messages),this.row.setAttribute("data-type","file"),this.row.setAttribute("data-file-uid",String(t.upload[0].uid)),this.fileName.textContent=t.upload[0].name,this.progressBar.value=100,this.progressBar.label=r.get("file_upload.uploadSucceeded"),this.progressBar.severity=c.ok;const s=String(t.upload[0].id);if(this.selector){const o=this.selector.querySelector("input");o&&(o.removeAttribute("disabled"),o.setAttribute("name","CBC[_FILE|"+x.hash(s)+"]"),o.setAttribute("value",s))}t.upload[0].icon&&(this.iconCol.innerHTML='<button type="button" class="btn btn-link" data-contextmenu-trigger="click" data-contextmenu-uid="'+s+'" data-contextmenu-table="sys_file" aria-label="'+r.get("labels.contextMenu.open")+'">'+t.upload[0].icon+"</span></button>"),this.dragUploader.irreObjectUid?(p.addFileToIrre(this.dragUploader.irreObjectUid,t.upload[0]),setTimeout(()=>{this.row.remove(),this.dragUploader.fileList.querySelectorAll("tr").length===0&&(this.dragUploader.fileList.setAttribute("hidden","hidden"),this.dragUploader.fileList.closest(".t3-filelist-container")?.classList.add("hidden"),this.dragUploader.trigger?.dispatchEvent(new CustomEvent("uploadSuccess",{detail:[this,t]})))},3e3)):setTimeout(()=>{this.showFileInfo(t.upload[0]),this.dragUploader.trigger?.dispatchEvent(new CustomEvent("uploadSuccess",{detail:[this,t]}))},3e3)}}showFileInfo(t){if(this.removeProgress(),document.querySelector("#filelist-searchterm")?.value){const i=document.createElement("td");i.textContent=t.path,this.row.append(i)}const s=document.createElement("td");s.classList.add("col-control"),this.row.append(s);const o=document.createElement("td");o.textContent=U.get("file")+" ("+t.extension.toUpperCase()+")",this.row.append(o);const e=document.createElement("td");if(e.textContent=u.fileSizeAsString(t.size),this.row.append(e),this.mode===d.MANAGE){let i="";t.permissions.read&&(i+='<strong class="text-danger">'+v.get("read")+"</strong>"),t.permissions.write&&(i+='<strong class="text-danger">'+v.get("write")+"</strong>");const a=document.createElement("td");a.innerHTML=i,this.row.append(a);const l=document.createElement("td");l.textContent="-",this.row.append(l)}for(let i=this.row.querySelectorAll("td").length;i<this.dragUploader.fileListColumnCount;i++)this.row.append(document.createElement("td"))}isProhibitedByAllowedExtensionsList(){if(!this.dragUploader.filesExtensionsAllowed)return!1;const t=this.file.name.split(".").pop();return!this.dragUploader.filesExtensionsAllowed.split(",").includes(t.toLowerCase())}isProhibitedByDisallowedExtensionsList(){if(!this.dragUploader.filesExtensionsDisallowed)return!1;const t=this.file.name.split(".").pop();return this.dragUploader.filesExtensionsDisallowed.split(",").includes(t.toLowerCase())}}p.init();export{p as default};