// Toast Notification const Toast = { show(message, type = 'success') { const container = document.getElementById('toast-container') || this.createContainer(); const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.style.cssText = ` background: var(--bg-surface); color: var(--text-primary); padding: 12px 24px; border-radius: 8px; margin-top: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); font-size: 14px; display: flex; align-items: center; gap: 8px; transform: translateY(-20px); opacity: 0; transition: all 0.3s; border-left: 4px solid ${type === 'error' ? 'var(--danger-color)' : 'var(--success-color)'}; `; // Icon const icon = type === 'error' ? '' : ''; toast.innerHTML = `${icon}${message}`; container.appendChild(toast); // Animate in requestAnimationFrame(() => { toast.style.transform = 'translateY(0)'; toast.style.opacity = '1'; }); // Auto remove setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateY(-20px)'; setTimeout(() => toast.remove(), 300); }, 3000); }, createContainer() { const div = document.createElement('div'); div.id = 'toast-container'; div.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 2100; display: flex; flex-direction: column; align-items: center; `; document.body.appendChild(div); return div; } }; // Modal System const Modal = { init() { if (document.getElementById('app-modal-overlay')) return; const overlay = document.createElement('div'); overlay.id = 'app-modal-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 2000; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.2s; `; const card = document.createElement('div'); card.id = 'app-modal-card'; card.style.cssText = ` background: var(--bg-surface); width: 90%; max-width: 320px; border-radius: var(--radius-lg); padding: 24px; box-shadow: var(--shadow-md); transform: scale(0.95); transition: transform 0.2s; `; card.innerHTML = `
`; overlay.appendChild(card); document.body.appendChild(overlay); this.overlay = overlay; this.card = card; this.titleEl = card.querySelector('#modal-title'); this.msgEl = card.querySelector('#modal-msg'); this.cancelBtn = card.querySelector('#modal-cancel'); this.confirmBtn = card.querySelector('#modal-confirm'); }, confirm(title, message) { this.init(); return new Promise((resolve) => { this.titleEl.textContent = title; this.msgEl.textContent = message; this.overlay.style.display = 'flex'; // Force reflow this.overlay.offsetHeight; this.overlay.style.opacity = '1'; this.card.style.transform = 'scale(1)'; const close = (result) => { this.overlay.style.opacity = '0'; this.card.style.transform = 'scale(0.95)'; setTimeout(() => { this.overlay.style.display = 'none'; resolve(result); }, 200); }; this.confirmBtn.onclick = () => close(true); this.cancelBtn.onclick = () => close(false); this.overlay.onclick = (e) => { if (e.target === this.overlay) close(false); }; }); } }; // 通用工具 const Utils = { async copy(text) { // 如果 text 是相对路径,自动转换为完整 URL if (text.startsWith('/')) { text = window.location.origin + text; } const successToast = () => Toast.show('已复制到剪贴板'); const failToast = () => Toast.show('复制失败,请手动复制', 'error'); // 优先使用 navigator.clipboard if (navigator.clipboard && navigator.clipboard.writeText) { try { await navigator.clipboard.writeText(text); successToast(); return true; } catch (err) { console.warn('Clipboard API failed, trying fallback...', err); } } // Fallback: document.execCommand('copy') try { const textarea = document.createElement('textarea'); textarea.value = text; // 必须可见才能被 select,使用 fixed 移出可视区但保持渲染 textarea.style.position = 'fixed'; textarea.style.left = '0'; textarea.style.top = '0'; textarea.style.opacity = '0.01'; textarea.style.pointerEvents = 'none'; textarea.setAttribute('readonly', ''); // 防止软键盘弹出 document.body.appendChild(textarea); textarea.focus(); textarea.select(); const successful = document.execCommand('copy'); document.body.removeChild(textarea); if (successful) { successToast(); return true; } } catch (fallbackErr) { console.error('Fallback copy failed:', fallbackErr); } // Final fallback: Modal with text try { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; align-items: center; justify-content: center; `; const content = document.createElement('div'); content.style.cssText = ` background: var(--bg-surface); padding: 24px; border-radius: 12px; width: 90%; max-width: 320px; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.15); `; content.innerHTML = `

复制失败,请长按手动复制:

${text}
`; modal.appendChild(content); document.body.appendChild(modal); const closeBtn = content.querySelector('button'); const close = () => modal.remove(); closeBtn.onclick = close; modal.onclick = (e) => { if(e.target === modal) close(); }; } catch (e) {} return false; }, setLoading(btn, isLoading) { if (!btn) return; if (isLoading) { btn.dataset.originalText = btn.innerHTML; btn.innerHTML = ` 处理中...`; btn.classList.add('loading'); btn.disabled = true; } else { btn.innerHTML = btn.dataset.originalText || btn.innerHTML; btn.classList.remove('loading'); btn.disabled = false; } } }; // 复制文件链接的辅助函数 window.copyLink = (shortId, fileId, filename) => { // 回滚:只生成 /d/{id},不带文件名/slug const id = (shortId && shortId !== 'None' && shortId !== '') ? shortId : fileId; const path = `/d/${id}`; Utils.copy(path); }; // 认证系统 const Auth = { async logout() { const confirmed = await Modal.confirm('退出登录', '确定要退出当前账号吗?'); if (!confirmed) return; try { const res = await fetch('/api/auth/logout', { method: 'POST', // 确保携带凭证(Cookies) credentials: 'include' }); if (res.ok) { // 清理可能存在的本地状态 // localStorage.removeItem('some_key'); // 强制跳转到登录页,并替换历史记录,防止后退 window.location.replace('/login'); } else { Toast.show('退出失败,请刷新重试', 'error'); } } catch (e) { console.error(e); Toast.show('网络错误', 'error'); } } }; // Theme System const Theme = { init() { const pref = localStorage.getItem('tgstate_theme_pref') || 'auto'; this.apply(pref); }, apply(mode) { let theme = mode; if (mode === 'auto') { theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } document.documentElement.setAttribute('data-theme', theme); // Update Toggle Button Text/Icon if needed (optional) const label = document.querySelector('.theme-label'); if (label) { label.textContent = mode === 'auto' ? '跟随系统' : (mode === 'dark' ? '深色模式' : '浅色模式'); } }, cycle() { const current = localStorage.getItem('tgstate_theme_pref') || 'auto'; const next = current === 'auto' ? 'light' : (current === 'light' ? 'dark' : 'auto'); localStorage.setItem('tgstate_theme_pref', next); this.apply(next); const modeNames = { 'auto': '跟随系统', 'light': '浅色模式', 'dark': '深色模式' }; Toast.show(`已切换到${modeNames[next]}`); } }; // 初始化 document.addEventListener('DOMContentLoaded', () => { Theme.init(); // 绑定主题切换按钮点击事件 document.querySelectorAll('.theme-toggle-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); Theme.cycle(); }); }); // 侧边栏/移动端菜单切换 const toggleBtn = document.querySelector('.menu-toggle'); const sidebar = document.querySelector('.sidebar'); const overlay = document.querySelector('.sidebar-overlay'); if (toggleBtn && sidebar) { toggleBtn.addEventListener('click', () => { sidebar.classList.toggle('active'); if (overlay) overlay.classList.toggle('active'); }); } if (overlay) { overlay.addEventListener('click', () => { sidebar.classList.remove('active'); overlay.classList.remove('active'); }); } }); // Expose to window for inline onclick handlers window.Theme = Theme; window.Auth = Auth; window.Utils = Utils; window.Modal = Modal; window.Toast = Toast;