1/** 2 * Dropzone.js Auto-Initializer 3 * 4 * Inicializa automáticamente dropzones con data attributes. 5 * Uso: <div class="dropzone" data-dropzone-input="input-id"> 6 * 7 * Requiere un input file oculto con el id especificado: 8 * <input type="file" id="input-id" multiple class="d-none"> 9 */ 10 11// Inyectar estilos para ocultar barra de progreso (no aplica con autoProcessQueue: false) 12(function() { 13 if (document.getElementById('dropzone-queued-styles')) return; 14 const style = document.createElement('style'); 15 style.id = 'dropzone-queued-styles'; 16 style.textContent = ` 17 /* Ocultar barra de progreso cuando no hay upload automático */ 18 [data-dropzone-input] .dz-progress { display: none !important; } 19 /* Mostrar archivo como "listo" */ 20 [data-dropzone-input] .dz-preview { opacity: 1 !important; } 21 [data-dropzone-input] .dz-preview .dz-details { opacity: 1 !important; } 22 /* Mantener mensaje visible cuando hay archivos */ 23 [data-dropzone-input].dz-started .dz-message { 24 display: block !important; 25 opacity: 0.7; 26 margin-bottom: 0.5rem; 27 width: 100%; 28 text-align: center; 29 } 30 [data-dropzone-input].dz-started .dz-message:hover { 31 opacity: 1; 32 } 33 /* Layout: mensaje arriba, previews en fila abajo */ 34 [data-dropzone-input].dz-started { 35 display: flex; 36 flex-wrap: wrap; 37 justify-content: center; 38 align-items: flex-start; 39 } 40 [data-dropzone-input] .dz-message { 41 flex-basis: 100%; /* Mensaje ocupa toda la fila */ 42 order: -1; 43 } 44 /* Previews en fila */ 45 [data-dropzone-input] .dz-preview { 46 display: inline-block; 47 margin: 0.5rem; 48 } 49 `; 50 document.head.appendChild(style); 51})(); 52 53function initDropzones() { 54 if (typeof Dropzone === 'undefined') { 55 console.warn('Dropzone no está cargado'); 56 return; 57 } 58 59 Dropzone.autoDiscover = false; 60 61 document.querySelectorAll('[data-dropzone-input]').forEach(function(element) { 62 // Evitar reinicializar 63 if (element.dropzone) return; 64 65 const inputId = element.dataset.dropzoneInput; 66 const fileInput = document.getElementById(inputId); 67 68 if (!fileInput) { 69 console.warn('Dropzone: input no encontrado:', inputId); 70 return; 71 } 72 73 const dataTransfer = new DataTransfer(); 74 75 new Dropzone(element, { 76 url: "#", 77 autoProcessQueue: false, 78 uploadMultiple: true, 79 parallelUploads: 10, 80 maxFilesize: 5, 81 maxFiles: 10, 82 acceptedFiles: ".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.gif,.zip", 83 addRemoveLinks: true, 84 dictDefaultMessage: "", 85 dictRemoveFile: "Quitar", 86 dictCancelUpload: "Cancelar", 87 dictInvalidFileType: "Tipo de archivo no permitido", 88 dictFileTooBig: "Archivo muy grande (máx 5MB)", 89 dictMaxFilesExceeded: "Máximo 10 archivos", 90 init: function() { 91 this.on("addedfile", function(file) { 92 dataTransfer.items.add(file); 93 fileInput.files = dataTransfer.files; 94 }); 95 96 this.on("removedfile", function(file) { 97 const newDataTransfer = new DataTransfer(); 98 Array.from(dataTransfer.files).forEach(function(f) { 99 if (f !== file) { 100 newDataTransfer.items.add(f); 101 } 102 }); 103 dataTransfer.items.clear(); 104 Array.from(newDataTransfer.files).forEach(function(f) { 105 dataTransfer.items.add(f); 106 }); 107 fileInput.files = dataTransfer.files; 108 }); 109 } 110 }); 111 }); 112} 113 114// Inicializar al cargar 115initDropzones(); 116 117// Re-inicializar después de swaps HTMX (para modales cargados dinámicamente) 118document.body.addEventListener('htmx:afterSettle', function(evt) { 119 if (evt.detail.target.querySelector('[data-dropzone-input]')) { 120 // Delay mÃnimo para asegurar que el DOM está listo 121 setTimeout(initDropzones, 10); 122 } 123});
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.