NoteJS/public/profile.js
Fovway bfa15465f8 Добавлены функции кэширования аватарок для улучшения производительности
- Реализованы функции для кэширования аватарок пользователей с использованием localStorage.
- Добавлены методы для получения, очистки и преобразования аватарок в формат base64.
- Обновлены интерфейсы загрузки и отображения аватарок с поддержкой кэширования.
- Обновлены зависимости, включая добавление библиотеки sharp для обработки изображений.
2025-10-24 23:14:18 +07:00

454 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// DOM элементы
const avatarImage = document.getElementById("avatarImage");
const avatarPlaceholder = document.getElementById("avatarPlaceholder");
const avatarInput = document.getElementById("avatarInput");
const deleteAvatarBtn = document.getElementById("deleteAvatarBtn");
const usernameInput = document.getElementById("username");
const emailInput = document.getElementById("email");
const updateProfileBtn = document.getElementById("updateProfileBtn");
const currentPasswordInput = document.getElementById("currentPassword");
const newPasswordInput = document.getElementById("newPassword");
const confirmPasswordInput = document.getElementById("confirmPassword");
const changePasswordBtn = document.getElementById("changePasswordBtn");
const messageContainer = document.getElementById("messageContainer");
// Кэширование аватарки
const AVATAR_CACHE_KEY = "avatar_cache";
const AVATAR_TIMESTAMP_KEY = "avatar_timestamp";
// Функция для кэширования аватарки
async function cacheAvatar(avatarUrl) {
try {
const response = await fetch(avatarUrl);
if (!response.ok) return false;
const blob = await response.blob();
const base64 = await blobToBase64(blob);
const cacheData = {
base64: base64,
timestamp: Date.now(),
url: avatarUrl
};
localStorage.setItem(AVATAR_CACHE_KEY, JSON.stringify(cacheData));
localStorage.setItem(AVATAR_TIMESTAMP_KEY, Date.now().toString());
return true;
} catch (error) {
console.error("Ошибка кэширования аватарки:", error);
return false;
}
}
// Функция для получения аватарки из кэша
function getCachedAvatar() {
try {
const cacheData = localStorage.getItem(AVATAR_CACHE_KEY);
const timestamp = localStorage.getItem(AVATAR_TIMESTAMP_KEY);
if (!cacheData || !timestamp) return null;
const data = JSON.parse(cacheData);
const cacheAge = Date.now() - parseInt(timestamp);
// Кэш действителен 24 часа (86400000 мс)
if (cacheAge > 24 * 60 * 60 * 1000) {
clearAvatarCache();
return null;
}
return data;
} catch (error) {
console.error("Ошибка получения аватарки из кэша:", error);
clearAvatarCache();
return null;
}
}
// Функция для очистки кэша аватарки
function clearAvatarCache() {
localStorage.removeItem(AVATAR_CACHE_KEY);
localStorage.removeItem(AVATAR_TIMESTAMP_KEY);
}
// Преобразование Blob в base64
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// Lazy loading для изображений
function initLazyLoading() {
// Проверяем поддержку Intersection Observer API
if ("IntersectionObserver" in window) {
const imageObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
// Если у изображения есть data-src, загружаем его
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute("data-src");
}
img.classList.remove("lazy");
observer.unobserve(img);
}
});
},
{
rootMargin: "50px 0px",
threshold: 0.01,
}
);
// Наблюдаем за всеми изображениями с классом lazy
document.querySelectorAll("img.lazy").forEach((img) => {
imageObserver.observe(img);
});
} else {
// Fallback для старых браузеров
document.querySelectorAll("img.lazy").forEach((img) => {
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute("data-src");
}
img.classList.remove("lazy");
});
}
}
// Функция для показа сообщений
function showMessage(message, type = "success") {
messageContainer.textContent = message;
messageContainer.className = `message-container ${type}`;
messageContainer.style.display = "block";
setTimeout(() => {
messageContainer.style.display = "none";
}, 5000);
}
// Загрузка данных профиля
async function loadProfile() {
try {
const response = await fetch("/api/user");
if (!response.ok) {
throw new Error("Ошибка загрузки профиля");
}
const user = await response.json();
// Заполняем поля
usernameInput.value = user.username || "";
emailInput.value = user.email || "";
// Применяем цветовой акцент пользователя (для отображения)
const accentColor = user.accent_color || "#007bff";
document.documentElement.style.setProperty("--accent-color", accentColor);
// Обрабатываем аватарку
if (user.avatar) {
// Проверяем, есть ли аватарка в кэше
const cachedAvatar = getCachedAvatar();
if (cachedAvatar && cachedAvatar.url === user.avatar) {
// Используем кэшированную аватарку
avatarImage.src = cachedAvatar.base64;
console.log("Аватарка загружена из кэша");
} else {
// Загружаем аватарку с сервера и кэшируем
avatarImage.src = user.avatar;
// Кэшируем в фоне
cacheAvatar(user.avatar).then(success => {
if (success) {
console.log("Аватарка закэширована");
}
});
}
avatarImage.style.display = "block";
avatarPlaceholder.style.display = "none";
deleteAvatarBtn.style.display = "inline-block";
} else {
avatarImage.style.display = "none";
avatarPlaceholder.style.display = "inline-flex";
deleteAvatarBtn.style.display = "none";
// Очищаем кэш, если аватарки нет
clearAvatarCache();
}
} catch (error) {
console.error("Ошибка загрузки профиля:", error);
showMessage("Ошибка загрузки данных профиля", "error");
}
}
// Обработчик загрузки аватарки
avatarInput.addEventListener("change", async function (event) {
const file = event.target.files[0];
if (!file) return;
// Проверка размера файла (5MB)
if (file.size > 5 * 1024 * 1024) {
showMessage("Файл слишком большой. Максимальный размер: 5 МБ", "error");
return;
}
// Проверка типа файла
const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif"];
if (!allowedTypes.includes(file.type)) {
showMessage(
"Недопустимый формат файла. Используйте JPG, PNG или GIF",
"error"
);
return;
}
try {
const formData = new FormData();
formData.append("avatar", file);
const response = await fetch("/api/user/avatar", {
method: "POST",
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Ошибка загрузки аватарки");
}
const result = await response.json();
// Обновляем отображение аватарки
avatarImage.src = result.avatar + "?t=" + Date.now(); // Добавляем timestamp для обновления кэша браузера
avatarImage.style.display = "block";
avatarPlaceholder.style.display = "none";
deleteAvatarBtn.style.display = "inline-block";
// Обновляем кэш с новой аватаркой
cacheAvatar(result.avatar).then(success => {
if (success) {
console.log("Новая аватарка закэширована");
}
});
showMessage("Аватарка успешно загружена", "success");
} catch (error) {
console.error("Ошибка загрузки аватарки:", error);
showMessage(error.message, "error");
}
// Сбрасываем input для возможности повторной загрузки того же файла
event.target.value = "";
});
// Обработчик удаления аватарки
deleteAvatarBtn.addEventListener("click", async function () {
if (!confirm("Вы уверены, что хотите удалить аватарку?")) {
return;
}
try {
const response = await fetch("/api/user/avatar", {
method: "DELETE",
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Ошибка удаления аватарки");
}
// Обновляем отображение
avatarImage.style.display = "none";
avatarPlaceholder.style.display = "inline-flex";
deleteAvatarBtn.style.display = "none";
// Очищаем кэш аватарки
clearAvatarCache();
showMessage("Аватарка успешно удалена", "success");
} catch (error) {
console.error("Ошибка удаления аватарки:", error);
showMessage(error.message, "error");
}
});
// Обработчик обновления профиля
updateProfileBtn.addEventListener("click", async function () {
const username = usernameInput.value.trim();
const email = emailInput.value.trim();
// Валидация
if (!username) {
showMessage("Логин не может быть пустым", "error");
return;
}
if (username.length < 3) {
showMessage("Логин должен быть не менее 3 символов", "error");
return;
}
if (email && !isValidEmail(email)) {
showMessage("Некорректный email адрес", "error");
return;
}
try {
const response = await fetch("/api/user/profile", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username,
email: email || null,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Ошибка обновления профиля");
}
const result = await response.json();
showMessage(result.message || "Профиль успешно обновлен", "success");
} catch (error) {
console.error("Ошибка обновления профиля:", error);
showMessage(error.message, "error");
}
});
// Обработчик изменения пароля
changePasswordBtn.addEventListener("click", async function () {
const currentPassword = currentPasswordInput.value;
const newPassword = newPasswordInput.value;
const confirmPassword = confirmPasswordInput.value;
// Валидация
if (!currentPassword) {
showMessage("Введите текущий пароль", "error");
return;
}
if (!newPassword) {
showMessage("Введите новый пароль", "error");
return;
}
if (newPassword.length < 6) {
showMessage("Новый пароль должен быть не менее 6 символов", "error");
return;
}
if (newPassword !== confirmPassword) {
showMessage("Новый пароль и подтверждение не совпадают", "error");
return;
}
try {
const response = await fetch("/api/user/profile", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
currentPassword,
newPassword,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Ошибка изменения пароля");
}
const result = await response.json();
// Очищаем поля паролей
currentPasswordInput.value = "";
newPasswordInput.value = "";
confirmPasswordInput.value = "";
showMessage(result.message || "Пароль успешно изменен", "success");
} catch (error) {
console.error("Ошибка изменения пароля:", error);
showMessage(error.message, "error");
}
});
// Функция валидации email
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
// Функция для проверки аутентификации
async function checkAuthentication() {
const isAuthenticated = localStorage.getItem("isAuthenticated");
if (isAuthenticated !== "true") {
// Если пользователь не аутентифицирован, перенаправляем на страницу входа
window.location.href = "/";
return;
}
// Проверяем, что сессия на сервере еще действительна
try {
const response = await fetch("/api/auth/status");
if (!response.ok) {
// Если сессия недействительна, очищаем localStorage и перенаправляем
localStorage.removeItem("isAuthenticated");
localStorage.removeItem("username");
window.location.href = "/";
return;
}
const authData = await response.json();
if (!authData.authenticated) {
// Если сервер говорит, что пользователь не аутентифицирован
localStorage.removeItem("isAuthenticated");
localStorage.removeItem("username");
window.location.href = "/";
return;
}
} catch (error) {
console.error("Ошибка проверки аутентификации:", error);
// В случае ошибки сети, оставляем пользователя на странице
// но показываем предупреждение
console.warn("Не удалось проверить статус аутентификации");
}
}
// Функция для настройки обработчика выхода
function setupLogoutHandler() {
const logoutForms = document.querySelectorAll('form[action="/logout"]');
logoutForms.forEach((form) => {
form.addEventListener("submit", function (e) {
// Очищаем localStorage перед выходом
localStorage.removeItem("isAuthenticated");
localStorage.removeItem("username");
});
});
}
// Загружаем профиль при загрузке страницы
document.addEventListener("DOMContentLoaded", function () {
// Проверяем аутентификацию при загрузке страницы
checkAuthentication();
loadProfile();
// Инициализируем lazy loading для изображений
initLazyLoading();
// Добавляем обработчик для кнопки выхода
setupLogoutHandler();
});