| const ThemeManager = { | |
| init() { | |
| const saved = localStorage.getItem('theme'); | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | |
| this.setTheme(saved || (prefersDark ? 'dark' : 'light')); | |
| window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { | |
| if (!localStorage.getItem('theme')) this.setTheme(e.matches ? 'dark' : 'light'); | |
| }); | |
| }, | |
| setTheme(t) { document.documentElement.setAttribute('data-theme', t); localStorage.setItem('theme', t); }, | |
| toggle() { this.setTheme(document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'); } | |
| }; | |
| const Toast = { | |
| container: null, | |
| init() { this.container = document.createElement('div'); this.container.className = 'toast-container'; document.body.appendChild(this.container); }, | |
| show(msg, type = 'success', dur = 2500) { | |
| const t = document.createElement('div'); | |
| t.className = `toast ${type}`; | |
| t.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">${type === 'success' ? '<path d="M20 6L9 17l-5-5"/>' : '<circle cx="12" cy="12" r="10"/><path d="M15 9l-6 6M9 9l6 6"/>'}</svg><span>${msg}</span>`; | |
| this.container.appendChild(t); | |
| setTimeout(() => { t.style.animation = 'slideIn 0.3s reverse'; setTimeout(() => t.remove(), 300); }, dur); | |
| } | |
| }; | |
| async function copyToClipboard(text, btn) { | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| Toast.show('Copied!'); | |
| if (btn) { const o = btn.innerHTML; btn.innerHTML = '✓ Copied'; setTimeout(() => btn.innerHTML = o, 1500); } | |
| } catch { Toast.show('Copy failed', 'error'); } | |
| } | |
| function toggleWrap() { | |
| const body = document.querySelector('.code-body'); | |
| const btn = document.getElementById('wrap-toggle'); | |
| if (!body) return; | |
| body.classList.toggle('wrap-text'); | |
| const isWrapped = body.classList.contains('wrap-text'); | |
| localStorage.setItem('wrapText', isWrapped); | |
| if (btn) btn.classList.toggle('btn-primary', isWrapped); | |
| } | |
| const API = { | |
| async request(url, opts = {}) { | |
| const r = await fetch(url, { headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', ...opts }); | |
| const d = await r.json(); | |
| if (!r.ok) throw new Error(d.error || 'Error'); | |
| return d; | |
| }, | |
| createPaste: (d) => API.request('/api/paste', { method: 'POST', body: JSON.stringify(d) }), | |
| updatePaste: (id, d) => API.request(`/api/paste/${id}`, { method: 'PUT', body: JSON.stringify(d) }), | |
| deletePaste: (id) => API.request(`/api/paste/${id}`, { method: 'DELETE' }), | |
| forkPaste: (id) => API.request(`/api/paste/${id}/fork`, { method: 'POST' }), | |
| login: (u, p) => API.request('/api/auth/login', { method: 'POST', body: JSON.stringify({ username: u, password: p }) }), | |
| register: (u, p) => API.request('/api/auth/register', { method: 'POST', body: JSON.stringify({ username: u, password: p }) }), | |
| logout: () => API.request('/api/auth/logout', { method: 'POST' }) | |
| }; | |
| function setupPasteForm() { | |
| const f = document.getElementById('paste-form'); | |
| if (!f) return; | |
| f.addEventListener('submit', async e => { | |
| e.preventDefault(); | |
| const btn = f.querySelector('button[type="submit"]'); | |
| try { | |
| btn.disabled = true; btn.textContent = 'Creating...'; | |
| const paste = await API.createPaste({ | |
| title: f.title.value || 'Untitled', content: f.content.value, language: f.language.value, | |
| is_public: f.is_public.checked, expires_in: f.expires_in?.value || 'never', burn_after_read: f.burn_after_read?.checked || false | |
| }); | |
| window.location.href = `/${paste.id}`; | |
| } catch (err) { Toast.show(err.message, 'error'); btn.disabled = false; btn.textContent = 'Create Paste'; } | |
| }); | |
| } | |
| function setupEditForm() { | |
| const f = document.getElementById('edit-form'); | |
| if (!f) return; | |
| f.addEventListener('submit', async e => { | |
| e.preventDefault(); | |
| const btn = f.querySelector('button[type="submit"]'); | |
| try { | |
| btn.disabled = true; btn.textContent = 'Saving...'; | |
| await API.updatePaste(f.dataset.pasteId, { title: f.title.value, content: f.content.value, language: f.language.value, is_public: f.is_public.checked }); | |
| Toast.show('Saved!'); setTimeout(() => window.location.href = `/${f.dataset.pasteId}`, 800); | |
| } catch (err) { Toast.show(err.message, 'error'); btn.disabled = false; btn.textContent = 'Save'; } | |
| }); | |
| } | |
| function setupAuthForms() { | |
| const login = document.getElementById('login-form'); | |
| const reg = document.getElementById('register-form'); | |
| if (login) login.addEventListener('submit', async e => { | |
| e.preventDefault(); | |
| const btn = login.querySelector('button[type="submit"]'); | |
| try { btn.disabled = true; await API.login(login.username.value, login.password.value); window.location.href = '/dashboard'; } | |
| catch (err) { Toast.show(err.message, 'error'); btn.disabled = false; } | |
| }); | |
| if (reg) reg.addEventListener('submit', async e => { | |
| e.preventDefault(); | |
| const btn = reg.querySelector('button[type="submit"]'); | |
| try { btn.disabled = true; await API.register(reg.username.value, reg.password.value); window.location.href = '/dashboard'; } | |
| catch (err) { Toast.show(err.message, 'error'); btn.disabled = false; } | |
| }); | |
| } | |
| function setupDeleteButton() { | |
| const btn = document.getElementById('delete-paste'); | |
| if (!btn) return; | |
| btn.addEventListener('click', async () => { | |
| if (!confirm('Delete this paste?')) return; | |
| try { await API.deletePaste(btn.dataset.pasteId); Toast.show('Deleted!'); setTimeout(() => window.location.href = '/dashboard', 800); } | |
| catch (err) { Toast.show(err.message, 'error'); } | |
| }); | |
| } | |
| function setupForkButton() { | |
| const btn = document.getElementById('fork-paste'); | |
| if (!btn) return; | |
| btn.addEventListener('click', async () => { | |
| try { const f = await API.forkPaste(btn.dataset.pasteId); Toast.show('Forked!'); setTimeout(() => window.location.href = `/${f.id}`, 800); } | |
| catch (err) { Toast.show(err.message, 'error'); } | |
| }); | |
| } | |
| function setupLogout() { | |
| const btn = document.getElementById('logout-btn'); | |
| if (!btn) return; | |
| btn.addEventListener('click', async e => { e.preventDefault(); try { await API.logout(); window.location.href = '/'; } catch { } }); | |
| } | |
| function setupKeyboardShortcuts() { | |
| document.addEventListener('keydown', e => { | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { | |
| const f = document.querySelector('#paste-form, #edit-form'); | |
| if (f) { e.preventDefault(); f.dispatchEvent(new Event('submit')); } | |
| } | |
| if ((e.ctrlKey || e.metaKey) && e.key === 's') { | |
| const f = document.getElementById('edit-form'); | |
| if (f) { e.preventDefault(); f.dispatchEvent(new Event('submit')); } | |
| } | |
| }); | |
| } | |
| function restoreWrapState() { | |
| if (localStorage.getItem('wrapText') === 'true') { | |
| const body = document.querySelector('.code-body'); | |
| const btn = document.getElementById('wrap-toggle'); | |
| if (body) body.classList.add('wrap-text'); | |
| if (btn) btn.classList.add('btn-primary'); | |
| } | |
| } | |
| document.addEventListener('DOMContentLoaded', () => { | |
| ThemeManager.init(); | |
| Toast.init(); | |
| setupPasteForm(); | |
| setupEditForm(); | |
| setupAuthForms(); | |
| setupDeleteButton(); | |
| setupForkButton(); | |
| setupLogout(); | |
| setupKeyboardShortcuts(); | |
| restoreWrapState(); | |
| }); | |
| window.toggleTheme = () => ThemeManager.toggle(); | |
| window.copyToClipboard = copyToClipboard; | |
| window.toggleWrap = toggleWrap; | |