// Переменные для пагинации логов let logsOffset = 0; const logsLimit = 50; let hasMoreLogs = true; // DOM элементы для внешнего вида const settingsAccentColorInput = document.getElementById( "settings-accentColor" ); const updateAppearanceBtn = document.getElementById("updateAppearanceBtn"); // Проверка аутентификации async function checkAuthentication() { try { const response = await fetch("/api/auth/status"); if (!response.ok) { localStorage.removeItem("isAuthenticated"); localStorage.removeItem("username"); window.location.href = "/"; return false; } const authData = await response.json(); if (!authData.authenticated) { localStorage.removeItem("isAuthenticated"); localStorage.removeItem("username"); window.location.href = "/"; return false; } return true; } catch (error) { console.error("Ошибка проверки аутентификации:", error); return false; } } // Загрузка информации о пользователе для применения accent color и заполнения формы async function loadUserInfo() { try { const response = await fetch("/api/user"); if (response.ok) { const user = await response.json(); const accentColor = user.accent_color || "#007bff"; // Применяем цветовой акцент if ( getComputedStyle(document.documentElement) .getPropertyValue("--accent-color") .trim() !== accentColor ) { document.documentElement.style.setProperty( "--accent-color", accentColor ); } // Заполняем поле цветового акцента в настройках if (settingsAccentColorInput) { settingsAccentColorInput.value = accentColor; updateColorPickerSelection(accentColor); } } } catch (error) { console.error("Ошибка загрузки информации о пользователе:", error); } } // Функция для обновления выбора цвета в цветовой палитре function updateColorPickerSelection(selectedColor) { const colorOptions = document.querySelectorAll( "#appearance-tab .color-option" ); colorOptions.forEach((option) => { option.classList.remove("selected"); if (option.dataset.color === selectedColor) { option.classList.add("selected"); } }); } // Функция для показа сообщений function showSettingsMessage(message, type = "success") { const container = document.getElementById("settings-message-container"); if (container) { container.textContent = message; container.className = `message-container ${type}`; container.style.display = "block"; setTimeout(() => { container.style.display = "none"; }, 5000); } } // Переключение табов function initTabs() { const tabs = document.querySelectorAll(".settings-tab"); const contents = document.querySelectorAll(".tab-content"); tabs.forEach((tab) => { tab.addEventListener("click", () => { const tabName = tab.dataset.tab; // Убираем активный класс со всех табов и контентов tabs.forEach((t) => t.classList.remove("active")); contents.forEach((c) => c.classList.remove("active")); // Добавляем активный класс к выбранному табу и контенту tab.classList.add("active"); document.getElementById(`${tabName}-tab`).classList.add("active"); // Загружаем данные для таба if (tabName === "archive") { loadArchivedNotes(); } else if (tabName === "logs") { loadLogs(true); } else if (tabName === "appearance") { // Данные внешнего вида уже загружены в loadUserInfo() } }); }); } // Загрузка архивных заметок async function loadArchivedNotes() { const container = document.getElementById("archived-notes-container"); container.innerHTML = '

Загрузка...

'; try { const response = await fetch("/api/notes/archived"); if (!response.ok) { throw new Error("Ошибка загрузки архивных заметок"); } const notes = await response.json(); if (notes.length === 0) { container.innerHTML = '

Архив пуст

'; return; } container.innerHTML = ""; notes.forEach((note) => { const noteDiv = document.createElement("div"); noteDiv.className = "archived-note-item"; noteDiv.dataset.noteId = note.id; // Форматируем дату const created = new Date(note.created_at.replace(" ", "T") + "Z"); const dateStr = new Intl.DateTimeFormat("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", }).format(created); // Преобразуем markdown в HTML для предпросмотра const htmlContent = marked.parse(note.content); const preview = htmlContent.substring(0, 200) + (htmlContent.length > 200 ? "..." : ""); // Изображения let imagesHtml = ""; if (note.images && note.images.length > 0) { imagesHtml = `
${note.images.length} изображений
`; } noteDiv.innerHTML = `
${dateStr}
${preview}
${imagesHtml} `; container.appendChild(noteDiv); }); // Добавляем обработчики событий addArchivedNotesEventListeners(); } catch (error) { console.error("Ошибка загрузки архивных заметок:", error); container.innerHTML = '

Ошибка загрузки архивных заметок

'; } } // Добавление обработчиков для архивных заметок function addArchivedNotesEventListeners() { // Восстановление document.querySelectorAll(".btn-restore").forEach((btn) => { btn.addEventListener("click", async (e) => { const noteId = e.target.closest("button").dataset.id; if (confirm("Восстановить эту заметку из архива?")) { try { const response = await fetch(`/api/notes/${noteId}/unarchive`, { method: "PUT", }); if (!response.ok) { throw new Error("Ошибка восстановления заметки"); } // Удаляем элемент из списка document.querySelector(`[data-note-id="${noteId}"]`)?.remove(); // Проверяем, остались ли заметки const container = document.getElementById("archived-notes-container"); if (container.children.length === 0) { container.innerHTML = '

Архив пуст

'; } alert("Заметка восстановлена!"); } catch (error) { console.error("Ошибка:", error); alert("Ошибка восстановления заметки"); } } }); }); // Окончательное удаление document.querySelectorAll(".btn-delete-permanent").forEach((btn) => { btn.addEventListener("click", async (e) => { const noteId = e.target.closest("button").dataset.id; if ( confirm("Удалить эту заметку НАВСЕГДА? Это действие нельзя отменить!") ) { try { const response = await fetch(`/api/notes/archived/${noteId}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Ошибка удаления заметки"); } // Удаляем элемент из списка document.querySelector(`[data-note-id="${noteId}"]`)?.remove(); // Проверяем, остались ли заметки const container = document.getElementById("archived-notes-container"); if (container.children.length === 0) { container.innerHTML = '

Архив пуст

'; } alert("Заметка удалена окончательно"); } catch (error) { console.error("Ошибка:", error); alert("Ошибка удаления заметки"); } } }); }); } // Загрузка логов async function loadLogs(reset = false) { if (reset) { logsOffset = 0; hasMoreLogs = true; } const tbody = document.getElementById("logsTableBody"); const loadMoreContainer = document.getElementById("logsLoadMore"); const filterValue = document.getElementById("logTypeFilter").value; if (reset) { tbody.innerHTML = 'Загрузка...'; } try { let url = `/api/logs?limit=${logsLimit}&offset=${logsOffset}`; if (filterValue) { url += `&action_type=${filterValue}`; } const response = await fetch(url); if (!response.ok) { throw new Error("Ошибка загрузки логов"); } const logs = await response.json(); if (reset) { tbody.innerHTML = ""; } if (logs.length === 0 && logsOffset === 0) { tbody.innerHTML = 'Логов пока нет'; loadMoreContainer.style.display = "none"; return; } if (logs.length < logsLimit) { hasMoreLogs = false; } logs.forEach((log) => { const row = document.createElement("tr"); // Форматируем дату const created = new Date(log.created_at.replace(" ", "T") + "Z"); const dateStr = new Intl.DateTimeFormat("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", }).format(created); // Форматируем тип действия const actionTypes = { login: "Вход", logout: "Выход", register: "Регистрация", note_create: "Создание заметки", note_update: "Редактирование", note_delete: "Удаление", note_pin: "Закрепление", note_archive: "Архивирование", note_unarchive: "Восстановление", note_delete_permanent: "Окончательное удаление", profile_update: "Обновление профиля", }; const actionText = actionTypes[log.action_type] || log.action_type; row.innerHTML = ` ${dateStr} ${actionText} ${log.details || "-"} ${log.ip_address || "-"} `; tbody.appendChild(row); }); logsOffset += logs.length; if (hasMoreLogs && logs.length > 0) { loadMoreContainer.style.display = "block"; } else { loadMoreContainer.style.display = "none"; } } catch (error) { console.error("Ошибка загрузки логов:", error); if (reset) { tbody.innerHTML = 'Ошибка загрузки логов'; } } } // Инициализация при загрузке страницы document.addEventListener("DOMContentLoaded", async function () { // Проверяем аутентификацию const isAuth = await checkAuthentication(); if (!isAuth) return; // Загружаем информацию о пользователе await loadUserInfo(); // Инициализируем табы initTabs(); // Обработчик фильтра логов document.getElementById("logTypeFilter").addEventListener("change", () => { loadLogs(true); }); // Обработчик кнопки обновления логов document.getElementById("refreshLogs").addEventListener("click", () => { loadLogs(true); }); // Обработчик кнопки "Загрузить еще" document.getElementById("loadMoreLogsBtn").addEventListener("click", () => { loadLogs(false); }); // Обработчик кнопки сохранения внешнего вида if (updateAppearanceBtn) { updateAppearanceBtn.addEventListener("click", async function () { const accentColor = settingsAccentColorInput.value; try { const response = await fetch("/api/user/profile", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ accent_color: accentColor, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Ошибка обновления цветового акцента"); } const result = await response.json(); // Применяем новый цветовой акцент document.documentElement.style.setProperty( "--accent-color", accentColor ); showSettingsMessage( result.message || "Цветовой акцент успешно обновлен", "success" ); } catch (error) { console.error("Ошибка обновления цветового акцента:", error); showSettingsMessage(error.message, "error"); } }); } // Обработчики для цветовой палитры function setupAppearanceColorPicker() { const colorOptions = document.querySelectorAll( "#appearance-tab .color-option" ); colorOptions.forEach((option) => { option.addEventListener("click", function () { const selectedColor = this.dataset.color; settingsAccentColorInput.value = selectedColor; updateColorPickerSelection(selectedColor); }); }); // Обработчик для input color if (settingsAccentColorInput) { settingsAccentColorInput.addEventListener("input", function () { updateColorPickerSelection(this.value); }); } } setupAppearanceColorPicker(); });