UNPKG

sortable-dnd-uploader

Version:

Drag and Drop media uploader with sorting and titles

134 lines (120 loc) 4.26 kB
class MediaUploader { constructor(container, options = {}) { this.container = container; this.options = options; this.dragSrcEl = null; this.init(); } init() { this.container.classList.add('sortable-dnd-uploader'); this.container.setAttribute('draggable', 'false'); this.container.addEventListener('dragstart', this.handleDragStart.bind(this), false); this.container.addEventListener('dragover', this.handleDragOver.bind(this), false); this.container.addEventListener('drop', this.handleDrop.bind(this), false); this.container.addEventListener('dragend', this.handleDragEnd.bind(this), false); this.container.addEventListener('click', this.handleClick.bind(this), false); this.container.addEventListener('dragenter', this.handleDragEnter.bind(this), false); this.container.addEventListener('dragleave', this.handleDragLeave.bind(this), false); // For file input fallback this.fileInput = document.createElement('input'); this.fileInput.type = 'file'; this.fileInput.multiple = true; this.fileInput.style.display = 'none'; this.fileInput.accept = this.options.accept || 'image/*,video/*'; this.fileInput.addEventListener('change', e => { this.handleFiles(e.target.files); e.target.value = ''; }); this.container.appendChild(this.fileInput); // For sorting, we will keep track of blocks inside container this.refreshBlocks(); } refreshBlocks() { this.blocks = Array.from(this.container.querySelectorAll('.sdu-block')); this.blocks.forEach(block => { block.setAttribute('draggable', 'true'); }); } handleDragStart(e) { if (!e.target.classList.contains('sdu-block')) return; this.dragSrcEl = e.target; e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/html', e.target.outerHTML); } catch {} e.target.classList.add('dragging'); } handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; const target = e.target.closest('.sdu-block'); if (target && target !== this.dragSrcEl) { const rect = target.getBoundingClientRect(); const next = (e.clientY - rect.top) / (rect.bottom - rect.top) > 0.5; this.container.insertBefore(this.dragSrcEl, next ? target.nextSibling : target); this.refreshBlocks(); } } handleDrop(e) { e.preventDefault(); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { this.handleFiles(e.dataTransfer.files); e.dataTransfer.clearData(); } } handleDragEnd(e) { if (this.dragSrcEl) { this.dragSrcEl.classList.remove('dragging'); this.dragSrcEl = null; } } handleClick(e) { const block = e.target.closest('.sdu-block'); if (block) { this.fileInput.click(); this.currentEditBlock = block; } } handleFiles(files) { Array.from(files).forEach(file => { this.addMediaBlock(file); }); } addMediaBlock(file) { const reader = new FileReader(); reader.onload = e => { const ext = file.name.split('.').pop().toLowerCase(); let mediaHtml = ''; if (['mp4', 'webm', 'ogg'].includes(ext)) { mediaHtml = `<video controls src="${e.target.result}" class="sdu-media"></video>`; } else { mediaHtml = `<img src="${e.target.result}" class="sdu-media" alt="">`; } const block = document.createElement('div'); block.className = 'sdu-block'; block.setAttribute('draggable', 'true'); block.innerHTML = ` ${mediaHtml} <input type="text" class="sdu-title" placeholder="Enter title..."> <button class="sdu-delete" title="Delete">&times;</button> `; this.container.appendChild(block); this.attachBlockEvents(block); this.refreshBlocks(); }; reader.readAsDataURL(file); } attachBlockEvents(block) { const deleteBtn = block.querySelector('.sdu-delete'); deleteBtn.addEventListener('click', e => { e.stopPropagation(); block.remove(); this.refreshBlocks(); }); } } // Для подключения через <script> if (typeof window !== 'undefined') { window.MediaUploader = MediaUploader; } export default MediaUploader;