// Save content async function saveContent() { collectAllContent(); currentContent.page = currentPage; currentContent.lang = document.getElementById('langSelect').value; try { const formData = new FormData(); formData.append('action', 'save'); formData.append('page', currentPage); formData.append('content', JSON.stringify(currentContent)); formData.append('csrf_token', CSRF_TOKEN); const response = await fetch('api.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { showToast(t('toast.saved'), 'success'); isDirty = false; currentContent.lastModified = result.data.lastModified; if (result.data.seoHealth) { const lang = document.getElementById('langSelect').value; const page = document.getElementById('pageSelect').value; currentSeoHealth = result.data.seoHealth; setPageSeoHealth(lang, page, result.data.seoHealth); updateEditorSeoHealth(result.data.seoHealth); } document.getElementById('lastModified').textContent = t('editor.last_saved', {date: formatDateShort(result.data.lastModified)}); loadBackups(); } else { showToast(result.message, 'error'); } } catch (error) { showToast(t('toast.error_saving', {message: error.message}), 'error'); } } // Load backups async function loadBackups() { try { const response = await fetch(`api.php?action=backups&page=${currentPage}`); const result = await response.json(); if (result.success) { renderBackups(result.data); } } catch (error) { console.error('Error loading backups:', error); } } function renderBackups(backups) { const container = document.getElementById('backupList'); if (backups.length === 0) { container.innerHTML = '

' + t('backups.no_backups') + '

'; return; } container.innerHTML = backups.map(backup => `
${backup.date} ${backup.time}
`).join(''); } async function previewBackup(filename) { try { const response = await fetch(`api.php?action=preview-backup&backup=${filename}`); const result = await response.json(); if (result.success) { showModal( t('backups.view'), renderBackupPreview(result.data), closeModal, { html: true, modalClass: 'modal-backup-preview', confirmText: t('btn.close'), confirmClass: 'btn btn-primary' } ); } else { showToast(result.message, 'error'); } } catch (error) { showToast(t('toast.error_generic', {message: error.message}), 'error'); } } function flattenBackupPreviewFields(value, prefix, rows) { if (Array.isArray(value)) { if (value.length === 0) { rows.push({ name: prefix || '[]', value: '[]' }); return; } var allScalar = value.every(function(item) { return item === null || typeof item !== 'object'; }); if (allScalar) { rows.push({ name: prefix || '[]', value: value.map(formatBackupPreviewValue).join(', ') }); return; } value.forEach(function(item, index) { flattenBackupPreviewFields(item, (prefix ? prefix : '') + '[' + index + ']', rows); }); return; } if (value && typeof value === 'object') { var keys = Object.keys(value); if (keys.length === 0) { rows.push({ name: prefix || '{}', value: '{}' }); return; } keys.forEach(function(key) { flattenBackupPreviewFields(value[key], prefix ? prefix + '.' + key : key, rows); }); return; } rows.push({ name: prefix || 'value', value: formatBackupPreviewValue(value) }); } function formatBackupPreviewValue(value) { if (value === null) return 'null'; if (value === undefined) return ''; if (typeof value === 'boolean') return value ? 'true' : 'false'; if (typeof value === 'number') return String(value); var text = String(value); return text === '' ? '—' : text; } function renderBackupPreview(data) { var rows = []; flattenBackupPreviewFields(data, '', rows); if (!rows.length) { return '

Keine Inhalte im Backup gefunden.

'; } return '
' + rows.map(function(row) { return '
' + '
' + escapeHtml(row.name) + '
' + '
' + escapeHtml(row.value) + '
' + '
'; }).join('') + '
'; } function restoreBackup(filename) { showModal(t('modal.restore_backup'), t('modal.restore_backup_confirm'), async () => { try { const formData = new FormData(); formData.append('action', 'restore'); formData.append('backup', filename); formData.append('csrf_token', CSRF_TOKEN); const response = await fetch('api.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { showToast(t('toast.backup_restored'), 'success'); closeModal(); loadContent(); } else { showToast(result.message, 'error'); } } catch (error) { showToast(t('toast.error_generic', {message: error.message}), 'error'); } } ); } function deleteBackup(filename) { showModal(t('modal.delete_backup'), t('modal.delete_backup_confirm'), async () => { try { const formData = new FormData(); formData.append('action', 'delete-backup'); formData.append('backup', filename); formData.append('csrf_token', CSRF_TOKEN); const response = await fetch('api.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { showToast(t('toast.backup_deleted'), 'success'); closeModal(); loadBackups(); } else { showToast(result.message, 'error'); } } catch (error) { showToast(t('toast.error_generic', {message: error.message}), 'error'); } } ); } // Modal function closeAllComboboxes() { document.querySelectorAll('.rj-combobox__list:not([hidden])').forEach(function(list) { list.hidden = true; var root = list.closest('.rj-combobox'); if (root) { var input = root.querySelector('input[role="combobox"]'); if (input) input.setAttribute('aria-expanded', 'false'); } }); } function showModal(title, text, onConfirm, options) { options = options || {}; closeAllComboboxes(); var modalEl = document.querySelector('#modalOverlay .modal'); if (modalEl) { modalEl.className = 'modal' + (options.modalClass ? ' ' + options.modalClass : ''); } document.getElementById('modalTitle').textContent = title; var modalText = document.getElementById('modalText'); if (options.html) { modalText.innerHTML = text; } else { modalText.textContent = text; } document.getElementById('modalOverlay').style.display = 'flex'; document.getElementById('modalOverlay').setAttribute('aria-hidden', 'false'); var confirmBtn = document.getElementById('modalConfirm'); confirmBtn.onclick = onConfirm || closeModal; if (options.confirmText) confirmBtn.textContent = options.confirmText; if (options.confirmClass) confirmBtn.className = options.confirmClass; if (options.hideConfirm) confirmBtn.style.display = 'none'; setTimeout(() => { var focusTarget = options.focusSelector ? document.querySelector(options.focusSelector) : confirmBtn; (focusTarget || confirmBtn).focus(); }, 0); } function closeModal() { const overlay = document.getElementById('modalOverlay'); overlay.style.display = 'none'; overlay.setAttribute('aria-hidden', 'true'); const modalEl = overlay.querySelector('.modal'); if (modalEl) modalEl.className = 'modal'; // Reset confirm button to default state const btn = document.getElementById('modalConfirm'); btn.textContent = t('btn.confirm'); btn.className = 'btn btn-danger'; btn.disabled = false; btn.style.display = ''; } // Format date without seconds function formatDateShort(dateStr) { const d = new Date(dateStr); return rjFormatDate(d, 'datetime'); } // Toast function showToast(message, type = 'success') { const existing = document.querySelector('.toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.setAttribute('role', type === 'error' ? 'alert' : 'status'); toast.setAttribute('aria-live', type === 'error' ? 'assertive' : 'polite'); toast.setAttribute('aria-atomic', 'true'); toast.textContent = message; document.body.appendChild(toast); const duration = type === 'error' ? 6000 : 4000; setTimeout(() => { toast.classList.add('toast-fade-out'); toast.addEventListener('animationend', () => toast.remove()); }, duration); } // HTML escape function escapeHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // Init document.getElementById('langSelect').addEventListener('change', function() { onTopbarLanguageChanged(); const m = document.getElementById('langSelectMobile'); if (m) m.value = this.value; }); const langMobile = document.getElementById('langSelectMobile'); if (langMobile) { langMobile.addEventListener('change', function() { document.getElementById('langSelect').value = this.value; onTopbarLanguageChanged(); }); } const pageMobile = document.getElementById('pageSelectMobile'); if (pageMobile) { pageMobile.addEventListener('change', function() { document.getElementById('pageSelect').value = this.value; }); } document.getElementById('pageSelect').addEventListener('change', function() { if (pageMobile) pageMobile.value = this.value; }); const DASHBOARD_PATH = window.location.pathname.replace(/\/dashboard\.php$/, '/dashboard');