/** * Global state store (Alpine.store). * * Single source of truth for the file lists and shared actions, analogous to a * Pinia/Redux store in a framework app. Components read reactive getters and * dispatch actions here instead of owning their own duplicate state. */ (function () { function register() { Alpine.store('files', { files: { inbox: [], outbox: [] }, toast: { show: false, message: '', _timer: null }, async init() { await this.loadFiles(); }, async loadFiles() { try { const [inbox, outbox] = await Promise.all([ fetch('/api/files?dir=inbox').then((r) => r.json()), fetch('/api/files?dir=outbox').then((r) => r.json()), ]); this.files.inbox = inbox.files || []; this.files.outbox = outbox.files || []; } catch (e) { this.showToast('Failed to load files'); } }, showToast(message) { this.toast.message = message; this.toast.show = true; if (this.toast._timer) clearTimeout(this.toast._timer); this.toast._timer = setTimeout(() => { this.toast.show = false; }, 2500); }, async upload(dir, fileList) { if (!fileList || !fileList.length) return; for (const file of fileList) { const form = new FormData(); form.append('file', file); form.append('dir', dir); await fetch('/api/upload', { method: 'POST', body: form }); } this.showToast('File uploaded'); await this.loadFiles(); }, download(id) { window.location.href = '/api/files/' + id + '/download'; }, async deleteFile(id) { const confirmed = await Alpine.store('confirm').show({ title: 'Delete file', message: 'This file will be permanently removed. Continue?', danger: true, }); if (!confirmed) return; await fetch('/api/files/' + id, { method: 'DELETE' }); this.showToast('File deleted'); await this.loadFiles(); }, async share(id) { const res = await fetch('/api/files/' + id + '/share', { method: 'POST' }); const data = await res.json(); try { await navigator.clipboard.writeText(data.url); this.showToast('Share link copied to clipboard'); } catch (_) { window.prompt('Share URL:', data.url); } }, async logout() { await fetch('/api/logout', { method: 'POST' }); window.location.href = '/login'; }, }); } if (window.Alpine) register(); else document.addEventListener('alpine:init', register); })();