// API Base const API_BASE = '/api'; // Utility functions const api = { async get(endpoint) { const response = await fetch(`${API_BASE}${endpoint}`); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); }, async post(endpoint, data) { const response = await fetch(`${API_BASE}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); }, async put(endpoint, data) { const response = await fetch(`${API_BASE}${endpoint}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); }, async delete(endpoint) { const response = await fetch(`${API_BASE}${endpoint}`, { method: 'DELETE', }); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); }, }; // Toast notifications class Toast { static show(message, type = 'info', duration = 3000) { const toast = document.createElement('div'); toast.className = `toast toast--${type}`; toast.textContent = message; toast.style.cssText = ` position: fixed; bottom: 24px; right: 24px; background-color: var(--md-sys-color-surface); color: var(--md-sys-color-on-surface); padding: 16px 24px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 9999; animation: slideIn 0.3s ease; border-left: 4px solid var(--md-sys-color-primary); `; if (type === 'success') { toast.style.borderLeftColor = '#4caf50'; } else if (type === 'error') { toast.style.borderLeftColor = 'var(--md-sys-color-error)'; } document.body.appendChild(toast); setTimeout(() => { toast.style.animation = 'slideOut 0.3s ease'; setTimeout(() => toast.remove(), 300); }, duration); } } // Modal dialog class Modal { constructor(title, content) { this.title = title; this.content = content; this.element = null; } open() { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 9998; animation: fadeIn 0.2s ease; `; const dialog = document.createElement('div'); dialog.style.cssText = ` background-color: var(--md-sys-color-surface); border-radius: 12px; padding: 24px; max-width: 500px; width: 90%; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); animation: slideUp 0.3s ease; `; const titleEl = document.createElement('h2'); titleEl.textContent = this.title; titleEl.style.cssText = 'margin: 0 0 16px 0;'; const contentEl = document.createElement('div'); contentEl.innerHTML = this.content; contentEl.style.cssText = 'margin-bottom: 24px;'; dialog.appendChild(titleEl); dialog.appendChild(contentEl); modal.appendChild(dialog); modal.addEventListener('click', (e) => { if (e.target === modal) this.close(); }); this.element = modal; document.body.appendChild(modal); } close() { if (this.element) { this.element.style.animation = 'fadeOut 0.2s ease'; setTimeout(() => this.element.remove(), 200); } } } // Post editor class PostEditor { constructor(containerId) { this.container = document.getElementById(containerId); this.postId = null; this.init(); } init() { this.container.innerHTML = `
`; this.form = document.getElementById('post-form'); this.form.addEventListener('submit', (e) => this.handleSubmit(e)); document.getElementById('preview-btn').addEventListener('click', () => this.showPreview()); } async handleSubmit(e) { e.preventDefault(); const formData = { title: document.getElementById('title').value, content: document.getElementById('content').value, excerpt: document.getElementById('excerpt').value, type: document.getElementById('type').value, category: document.getElementById('category').value, tags: document.getElementById('tags').value.split(',').map(t => t.trim()).filter(t => t), published: document.getElementById('published').checked, }; try { const endpoint = this.postId ? `/posts/${this.postId}` : '/posts'; const method = this.postId ? 'put' : 'post'; const result = await api[method](endpoint, formData); if (result.success) { Toast.show('Post saved successfully!', 'success'); if (!this.postId) { this.postId = result.data.id; } setTimeout(() => window.location.href = '/', 1500); } } catch (error) { Toast.show('Error saving post: ' + error.message, 'error'); } } showPreview() { const content = document.getElementById('content').value; const title = document.getElementById('title').value; const previewHtml = `$1')
.replace(/\n\n/gm, '') .replace(/^/gm, '
') .replace(/$/gm, '
'); return html; } } // Posts list class PostsList { constructor(containerId) { this.container = document.getElementById(containerId); this.page = 1; this.loading = false; this.init(); } async init() { this.container.innerHTML = '${post.excerpt || post.content.substring(0, 200)}
Read more →