1 2// ÐаполнÑеÑÑÑ Ð¿Ñи загÑÑзке ÑÑÑаниÑÑ 3const taskFormConfig = { 4 formId: '', 5 sFileRemoveUrl: '', 6 sFileUploadUrl: '', 7 aImagesIds: [], 8 iImagesUploadLimit: 0, 9 allowedExtensions: [], 10}; 11 12function esc(s) { 13 return String(s).replace(/[&<>"]/g, c => ({'&': '&', '<': '<', '>': '>', '"': '"'}[c])); 14} 15 16const oFiles = { 17 aFilesIds: [], 18 count() { return this.aFilesIds.length; }, 19 uploadCount() { return Math.max(0, taskFormConfig.iImagesUploadLimit - this.count()); }, 20 add(iFileId) { 21 if (!this.uploadCount()) return; 22 iFileId = iFileId * 1; 23 for (let i = 0; i < this.aFilesIds.length; i++) { 24 if (this.aFilesIds[i] === iFileId) return; 25 } 26 this.aFilesIds.push(iFileId); 27 }, 28 del(iFileId) { 29 if (!this.count()) return; 30 iFileId = iFileId * 1; 31 this.aFilesIds = this.aFilesIds.filter(id => id !== iFileId); 32 }, 33 toString() { return JSON.stringify(this.aFilesIds); } 34}; 35 36document.addEventListener('DOMContentLoaded', function () { 37 oFiles.aFilesIds = taskFormConfig.aImagesIds.slice(); 38 39 const imagesSection = document.getElementById('imagesSection'); 40 const fileInput = document.getElementById('taskFiles'); 41 const previewContainer = document.getElementById('taskFilePreview'); 42 const dropzone = document.getElementById('taskFileDropzone'); 43 44 const allowedExtensions = taskFormConfig.allowedExtensions; 45 let pendingFiles = []; 46 47 // ÐÑÑÑем Ð·Ð¾Ð½Ñ Ð·Ð°Ð³ÑÑзки, когда доÑÑигнÑÑ Ð»Ð¸Ð¼Ð¸Ñ Ñайлов. 48 function updateDropzone() { 49 if (!dropzone) return; 50 const total = oFiles.count() + pendingFiles.length; 51 dropzone.style.display = total >= taskFormConfig.iImagesUploadLimit ? 'none' : ''; 52 } 53 54 // Удаление ÑÑÑеÑÑвÑÑÑÐ¸Ñ Ñайлов 55 if (imagesSection) { 56 imagesSection.addEventListener('click', function (e) { 57 const btn = e.target.closest('.remove-file'); 58 if (!btn) return; 59 e.preventDefault(); 60 61 const fileId = btn.getAttribute('data-fileid'); 62 63 swal({ 64 title: 'УдалиÑÑ Ñайл?', 65 icon: 'warning', 66 buttons: ['ÐÑмена', 'УдалиÑÑ'], 67 dangerMode: true, 68 }).then(function (confirmed) { 69 if (!confirmed) return; 70 71 btn.disabled = true; 72 73 const fd = new FormData(); 74 fd.append(yii.getCsrfParam(), yii.getCsrfToken()); 75 76 fetch(taskFormConfig.sFileRemoveUrl + '?' + new URLSearchParams({iFileId: fileId}), { 77 method: 'POST', 78 body: fd, 79 headers: {'X-Requested-With': 'XMLHttpRequest'} 80 }) 81 .then(r => r.json()) 82 .then(response => { 83 if (!response.success) { 84 alert(response.message || 'ÐÑибка ÑдалениÑ'); 85 btn.disabled = false; 86 return; 87 } 88 89 const tile = document.getElementById('fileId-' + fileId); 90 if (!tile) return; 91 tile.style.transition = 'opacity .3s'; 92 tile.style.opacity = '0'; 93 setTimeout(() => tile.remove(), 300); 94 95 oFiles.del(parseInt(fileId, 10)); 96 updateDropzone(); 97 }); 98 }); 99 }); 100 } 101 102 function processFiles(files) { 103 const available = oFiles.uploadCount() - pendingFiles.length; 104 if (available <= 0 || files.length > available) { 105 alert('Ðожно пÑикÑепиÑÑ ÐµÑÑ Ð½Ðµ более ' + available + ' Ñайл(ов). ÐимиÑ: ' + taskFormConfig.iImagesUploadLimit + ', Ñже загÑÑжено: ' + oFiles.count() + ', вÑбÑано: ' + pendingFiles.length + '.'); 106 return; 107 } 108 109 for (let i = 0; i < files.length; i++) { 110 const ext = files[i].name.split('.').pop().toLowerCase(); 111 if (!allowedExtensions.includes(ext)) { 112 alert('Файл ' + files[i].name + ' Ð¸Ð¼ÐµÐµÑ Ð½ÐµÐ´Ð¾Ð¿ÑÑÑимÑй ÑоÑмаÑ!'); 113 return; 114 } 115 } 116 117 pendingFiles = pendingFiles.concat(Array.from(files)); 118
119 files.forEach(function (file) { 120 const wrapper = document.createElement('div'); 121 wrapper.className = 'tfile tfile--pending'; 122 wrapper._pendingFile = file; 123 const ext = esc((file.name.split('.').pop() || 'file').toUpperCase()); 124 const name = esc(file.name); 125 126 function bindRemove() { 127 wrapper.querySelector('.tfile__remove').addEventListener('click', function () { 128 pendingFiles = pendingFiles.filter(f => f !== file); 129 wrapper.remove(); 130 updateDropzone(); 131 }); 132 } 133 134 if (file.type.startsWith('image/')) { 135 const reader = new FileReader(); 136 reader.onload = function (e) { 137 wrapper.innerHTML = ` 138 <div class="tfile__card"> 139 <div class="tfile__open"><img src="${e.target.result}" class="tfile__img" alt=""></div> 140 <button type="button" class="tfile__remove" title="УдалиÑÑ"><i class="fa fa-times" aria-hidden="true"></i></button> 141 </div> 142 <div class="tfile__name" title="${name}">${name}</div>`; 143 bindRemove(); 144 }; 145 reader.readAsDataURL(file); 146 } else { 147 wrapper.innerHTML = ` 148 <div class="tfile__card"> 149 <div class="tfile__open"><span class="tfile__doc"><i class="fa fa-file-o" aria-hidden="true"></i><span class="tfile__ext">${ext}</span></span></div> 150 <button type="button" class="tfile__remove" title="УдалиÑÑ"><i class="fa fa-times" aria-hidden="true"></i></button> 151 </div> 152 <div class="tfile__name" title="${name}">${name}</div>`; 153 bindRemove(); 154 } 155 156 previewContainer.appendChild(wrapper); 157 }); 158 159 updateDropzone(); 160 } 161 162 // Drag-over ÑÑÑÐµÐºÑ Ð½Ð° dropzone 163 if (dropzone && fileInput) { 164 let dragCounter = 0; 165 166 dropzone.addEventListener('dragenter', e => { e.preventDefault(); if (++dragCounter === 1) dropzone.classList.add('is-dragover'); }); 167 dropzone.addEventListener('dragleave', e => { e.preventDefault(); if (--dragCounter === 0) dropzone.classList.remove('is-dragover'); }); 168 dropzone.addEventListener('dragover', e => e.preventDefault()); 169 dropzone.addEventListener('drop', function (e) { 170 e.preventDefault(); 171 dragCounter = 0; 172 dropzone.classList.remove('is-dragover'); 173 const files = Array.from(e.dataTransfer.files); 174 if (files.length) processFiles(files); 175 }); 176 } 177 178 // ÐÑÐ±Ð¾Ñ Ñайлов ÑеÑез input â пÑевÑÑ 179 if (fileInput) { 180 fileInput.addEventListener('change', function () { 181 processFiles(Array.from(this.files)); 182 }); 183 } 184 185 updateDropzone(); 186 187 // ÐÑпÑавка ÑоÑмÑ: ÑнаÑала загÑÑжаем ÑайлÑ, поÑом ÑабмиÑим 188 const createTaskBtn = document.getElementById('createTaskBtn'); 189 if (createTaskBtn) { 190 createTaskBtn.addEventListener('click', function (e) { 191 e.preventDefault(); 192 193 if (pendingFiles.length === 0) { 194 submitForm(); 195 return; 196 } 197 198 createTaskBtn.disabled = true; 199 200 const uploadPromises = pendingFiles.map(function (file) { 201 const fd = new FormData(); 202 fd.append('FileUploadForm[file]', file); 203 fd.append(yii.getCsrfParam(), yii.getCsrfToken()); 204 return fetch(taskFormConfig.sFileUploadUrl, { 205 method: 'POST', 206 body: fd, 207 headers: {'X-Requested-With': 'XMLHttpRequest'} 208 }).then(r => r.json()); 209 }); 210 211 Promise.all(uploadPromises) 212 .then(function (responses) {
213 responses.forEach(function (response) { 214 if (response.success) { 215 oFiles.add(response.data.file.id); 216 } 217 }); 218 submitForm(); 219 }) 220 .catch(function () { 221 alert('ÐÑибка пÑи загÑÑзке Ñайлов'); 222 createTaskBtn.disabled = false; 223 }); 224 }); 225 } 226 227 function submitForm() { 228 document.getElementById('imagesSort').value = JSON.stringify(oFiles.aFilesIds); 229 var $form = $('#' + taskFormConfig.formId); 230 // 'form-ready-for-submit' даÑÑ Ð´ÑÑгим виджеÑам (напÑ. select.php â навÑки) 231 // ÑÐ¸Ð½Ñ Ñонно дополниÑÑ ÑоÑÐ¼Ñ Ð¿ÐµÑед оÑпÑавкой; Ñам ÑÐ°Ð±Ð¼Ð¸Ñ â вÑегда наÑа забоÑа. 232 $form.trigger('form-ready-for-submit'); 233 $form.get(0).submit(); 234 } 235});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.