'use strict'; // ── Helpers ──────────────────────────────────────────────────────────────── function formatBytes(n) { if (n < 1024) return n + ' B'; if (n < 1024 ** 2) return (n / 1024).toFixed(1) + ' KB'; if (n < 1024 ** 3) return (n / 1024 ** 2).toFixed(1) + ' MB'; return (n / 1024 ** 3).toFixed(2) + ' GB'; } function copyToClipboard(text, btn) { navigator.clipboard.writeText(text).then(() => { const orig = btn.textContent; btn.textContent = 'Copied!'; setTimeout(() => (btn.textContent = orig), 1500); }); } // ── DOM refs ─────────────────────────────────────────────────────────────── const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); const formView = document.getElementById('formView'); const resultView = document.getElementById('resultView'); const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); const dzIcon = document.getElementById('dzIcon'); const dzMain = document.getElementById('dzMain'); const dzHint = document.getElementById('dzHint'); const dzName = document.getElementById('dzName'); const dzSize = document.getElementById('dzSize'); const textArea = document.getElementById('textArea'); const ttlSelect = document.getElementById('ttlSelect'); const passwordIn = document.getElementById('passwordInput'); const burnToggle = document.getElementById('burnToggle'); const uploadBtn = document.getElementById('uploadBtn'); const progressWrap = document.getElementById('progressWrap'); const progressFill = document.getElementById('progressFill'); const progressLbl = document.getElementById('progressLbl'); const alertBox = document.getElementById('alertBox'); const resultUrl = document.getElementById('resultUrl'); const copyUrlBtn = document.getElementById('copyUrlBtn'); const resultPwWrap = document.getElementById('resultPwWrap'); const resultPw = document.getElementById('resultPw'); const copyPwBtn = document.getElementById('copyPwBtn'); const shareAgainBtn = document.getElementById('shareAgainBtn'); let selectedFile = null; let activeTab = 'file'; // ── Tabs ─────────────────────────────────────────────────────────────────── tabBtns.forEach(btn => { btn.addEventListener('click', () => { activeTab = btn.dataset.tab; tabBtns.forEach(b => b.classList.toggle('active', b === btn)); tabContents.forEach(c => c.classList.toggle('active', c.id === 'tab-' + activeTab)); clearAlert(); }); }); // ── Drop zone ────────────────────────────────────────────────────────────── dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover')); dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('dragover'); if (e.dataTransfer.files.length) setFile(e.dataTransfer.files[0]); }); fileInput.addEventListener('change', () => { if (fileInput.files.length) setFile(fileInput.files[0]); }); function setFile(file) { selectedFile = file; dropZone.classList.add('has-file'); dzIcon.textContent = '📄'; dzMain.textContent = ''; dzHint.textContent = ''; dzName.textContent = file.name; dzSize.textContent = formatBytes(file.size); } // ── Alerts ───────────────────────────────────────────────────────────────── function showAlert(msg, type = 'error') { alertBox.className = `alert alert-${type}`; alertBox.textContent = msg; alertBox.classList.remove('hidden'); } function clearAlert() { alertBox.classList.add('hidden'); } // ── Upload ───────────────────────────────────────────────────────────────── uploadBtn.addEventListener('click', handleUpload); async function handleUpload() { clearAlert(); const isFile = activeTab === 'file'; const password = passwordIn.value.trim(); if (isFile && !selectedFile) { showAlert('Please select a file first.'); return; } if (!isFile && !textArea.value.trim()) { showAlert('Please enter some text first.'); return; } uploadBtn.disabled = true; progressWrap.classList.remove('hidden'); setProgress(0, 'Preparing…'); try { const form = new FormData(); if (isFile) { form.append('file', selectedFile, selectedFile.name); form.append('share_type', 'file'); form.append('original_filename', selectedFile.name); form.append('mimetype', selectedFile.type || 'application/octet-stream'); } else { const blob = new Blob([textArea.value], { type: 'text/plain' }); form.append('file', blob, 'text.txt'); form.append('share_type', 'text'); form.append('mimetype', 'text/plain'); } form.append('ttl_hours', ttlSelect.value); form.append('burn_after_read', burnToggle.checked); if (password) form.append('password', password); setProgress(10, 'Uploading…'); const data = await xhrUpload('/api/upload', form, pct => { setProgress(10 + Math.round(pct * 0.88), `Uploading… ${Math.round(pct)}%`); }); setProgress(100, 'Done'); const shareUrl = `${location.origin}/s/${data.share_id}`; switchToResult(shareUrl, password || null); } catch (err) { showAlert(err.message || 'Upload failed. Please try again.'); uploadBtn.disabled = false; progressWrap.classList.add('hidden'); } } function setProgress(pct, label) { progressFill.style.width = pct + '%'; progressLbl.textContent = label; } function xhrUpload(url, formData, onProgress) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', url); xhr.upload.onprogress = e => { if (e.lengthComputable) onProgress((e.loaded / e.total) * 100); }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { let msg = 'Upload failed'; try { msg = JSON.parse(xhr.responseText).detail || msg; } catch {} reject(new Error(msg)); } }; xhr.onerror = () => reject(new Error('Network error')); xhr.send(formData); }); } // ── View crossfade ───────────────────────────────────────────────────────── function switchToResult(url, pw) { resultUrl.value = url; copyUrlBtn.onclick = () => copyToClipboard(url, copyUrlBtn); if (pw) { resultPwWrap.classList.remove('hidden'); resultPw.value = pw; copyPwBtn.onclick = () => copyToClipboard(pw, copyPwBtn); } else { resultPwWrap.classList.add('hidden'); } // Re-trigger SVG check animation ['.check-circle', '.check-mark'].forEach(sel => { const el = resultView.querySelector(sel); el.style.animation = 'none'; el.getBoundingClientRect(); el.style.animation = ''; }); formView.classList.add('view-exiting'); setTimeout(() => { formView.classList.add('hidden'); formView.classList.remove('view-exiting'); progressWrap.classList.add('hidden'); resultView.classList.remove('hidden'); resultView.classList.add('view-entering'); setTimeout(() => resultView.classList.remove('view-entering'), 350); }, 260); } function switchToForm() { resultView.classList.add('view-exiting'); setTimeout(() => { resultView.classList.add('hidden'); resultView.classList.remove('view-exiting'); resetForm(); formView.classList.remove('hidden'); formView.classList.add('view-entering'); setTimeout(() => formView.classList.remove('view-entering'), 350); }, 260); } function resetForm() { selectedFile = null; fileInput.value = ''; dropZone.classList.remove('has-file'); dzIcon.textContent = '📂'; dzMain.textContent = 'Drop a file or click to browse'; dzHint.textContent = '1 GB recommended · up to 6 GB accepted'; dzName.textContent = ''; dzSize.textContent = ''; textArea.value = ''; passwordIn.value = ''; burnToggle.checked = false; ttlSelect.value = '24'; clearAlert(); uploadBtn.disabled = false; } shareAgainBtn.addEventListener('click', switchToForm);