diff --git a/MOBILE-UPLOAD-TESTING.md b/MOBILE-UPLOAD-TESTING.md
deleted file mode 100644
index 0f7d1fc..0000000
--- a/MOBILE-UPLOAD-TESTING.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# 📱 Тестирование загрузки изображений на мобильных устройствах
-
-## Проблема
-Пользователи не могли загружать картинки в заметки с мобильных телефонов.
-
-## Внесенные исправления
-
-### 1. Улучшения JavaScript (app.js)
-- ✅ Добавлена поддержка touch событий для кнопки загрузки изображений
-- ✅ Улучшена обработка выбора файлов с проверкой размера и типа
-- ✅ Добавлена защита от дублирования файлов
-- ✅ Улучшена функция обновления превью с обработкой ошибок
-- ✅ Добавлены индикаторы загрузки для мобильных устройств
-- ✅ Улучшена функция сохранения заметок с уведомлениями
-
-### 2. Улучшения CSS (style.css)
-- ✅ Добавлены стили для touch устройств (touch-action, -webkit-tap-highlight-color)
-- ✅ Увеличена минимальная высота кнопок для удобства touch (44px+)
-- ✅ Улучшены размеры кнопок удаления изображений
-- ✅ Добавлены специальные стили для мобильных устройств в медиа-запросах
-
-### 3. Создана тестовая страница
-- ✅ `/test-mobile-upload.html` - специальная страница для тестирования загрузки на мобильных
-
-## Как протестировать
-
-### На мобильном устройстве:
-1. Откройте приложение в мобильном браузере
-2. Перейдите на страницу заметок
-3. Нажмите на кнопку загрузки изображений (📷)
-4. Выберите одно или несколько изображений
-5. Проверьте превью изображений
-6. Нажмите "Сохранить"
-7. Убедитесь, что изображения загрузились и отображаются в заметке
-
-### Альтернативный способ тестирования:
-1. Откройте `/test-mobile-upload.html` на мобильном устройстве
-2. Эта страница содержит специальные тесты для проверки загрузки файлов
-3. Проверьте все функции загрузки и отображения
-
-## Основные улучшения для мобильных устройств
-
-### Touch Events
-- Добавлена поддержка `touchend` событий
-- Улучшена обработка touch для кнопок
-
-### Размеры элементов
-- Минимальная высота кнопок: 44px (рекомендация Apple/Google)
-- Увеличены размеры кнопок удаления изображений
-- Улучшены отступы и размеры для touch
-
-### Визуальная обратная связь
-- Индикаторы загрузки для мобильных устройств
-- Уведомления об успешном сохранении
-- Обработка ошибок с понятными сообщениями
-
-### Производительность
-- Проверка размера файлов (максимум 10MB)
-- Защита от дублирования файлов
-- Обработка ошибок чтения файлов
-
-## Поддерживаемые форматы изображений
-- JPEG (.jpg, .jpeg)
-- PNG (.png)
-- GIF (.gif)
-- WebP (.webp)
-
-## Ограничения
-- Максимальный размер файла: 10MB
-- Максимальное количество файлов за раз: 10
-- Поддерживаются только изображения
-
-## Браузеры
-Протестировано на:
-- ✅ Chrome Mobile (Android)
-- ✅ Safari Mobile (iOS)
-- ✅ Firefox Mobile
-- ✅ Samsung Internet
-
-## Если проблемы остаются
-
-1. **Проверьте консоль браузера** на наличие ошибок JavaScript
-2. **Убедитесь, что у вас стабильное интернет-соединение**
-3. **Попробуйте уменьшить размер изображений** (сжать перед загрузкой)
-4. **Проверьте, что браузер поддерживает File API** (современные браузеры поддерживают)
-5. **Попробуйте перезагрузить страницу** и повторить попытку
-
-## Отладка
-Для отладки используйте:
-- `/test-mobile-upload.html` - специальная тестовая страница
-- Консоль разработчика в мобильном браузере
-- Информация об устройстве и браузере на тестовой странице
diff --git a/PWA-TESTING.md b/PWA-TESTING.md
deleted file mode 100644
index db52886..0000000
--- a/PWA-TESTING.md
+++ /dev/null
@@ -1,269 +0,0 @@
-# 🚀 Тестирование PWA для NoteJS
-
-## Что было сделано
-
-✅ **Созданы файлы PWA:**
-
-- `manifest.json` - манифест приложения
-- `sw.js` - сервис-воркер для кэширования
-- `pwa.js` - JavaScript класс для управления PWA
-- `icon.svg` - SVG иконка приложения
-- `logo.svg` - логотип приложения
-- `icons/` - PNG иконки различных размеров
-- `browserconfig.xml` - конфигурация для Windows
-
-✅ **Обновлены HTML страницы:**
-
-- Добавлены PWA мета-теги
-- Подключены иконки и манифест
-- Добавлен скрипт регистрации Service Worker
-
-✅ **Настроен сервер:**
-
-- Правильные заголовки для PWA файлов
-- Поддержка кэширования
-
-## Как протестировать
-
-### 1. Откройте диагностическую страницу PWA
-
-```
-http://localhost:3000/pwa-debug.html
-```
-
-### 2. Откройте тестовую страницу для мобильных устройств
-
-```
-http://localhost:3000/mobile-pwa-test.html
-```
-
-### 3. Откройте тестовую страницу для Brave браузера
-
-```
-http://localhost:3000/brave-pwa-test.html
-```
-
-### 4. Откройте обычную тестовую страницу
-
-```
-http://localhost:3000/test-pwa.html
-```
-
-### 5. Проверьте требования PWA
-
-На мобильной тестовой странице автоматически проверяются все требования:
-
-- ✅ HTTPS или localhost
-- ✅ Service Worker
-- ✅ Manifest
-- ✅ Иконки
-- ❌ Уже установлено (должно быть красным, если не установлено)
-
-### 6. Установка приложения
-
-- Если все проверки пройдены, появится кнопка "Установить приложение"
-- Нажмите на неё для установки PWA
-- Следуйте инструкциям браузера или используйте инструкции на странице
-
-### 7. Проверка в разных браузерах
-
-#### Chrome/Edge:
-
-- Откройте DevTools (F12)
-- Перейдите в Application → Manifest
-- Проверьте, что манифест загружается без ошибок
-- В Application → Service Workers проверьте статус SW
-
-#### Firefox:
-
-- Откройте DevTools (F12)
-- Перейдите в Application → Manifest
-- Проверьте манифест
-
-#### Brave:
-
-- Откройте `http://localhost:3000/brave-pwa-test.html` для диагностики
-- Проверьте настройки PWA в браузере
-- Используйте меню браузера для установки
-
-#### Safari (iOS):
-
-- Откройте сайт в Safari
-- Нажмите кнопку "Поделиться"
-- Выберите "На экран Домой"
-- Приложение установится как PWA
-
-#### ПК/Десктоп (Chrome, Edge, Firefox):
-
-- Откройте сайт в браузере
-- Нажмите на иконку установки в адресной строке (если доступна)
-- Или используйте меню браузера → "Установить приложение"
-- Или используйте меню браузера → "Создать ярлык"
-
-## Новые улучшения для мобильных устройств
-
-✅ **Улучшенный manifest.json:**
-
-- Добавлены все необходимые размеры иконок (72x72, 96x96, 128x128, 144x144, 152x152, 192x192, 384x384, 512x512)
-- Добавлены maskable иконки для Android
-- Добавлены категории и скриншоты
-- Улучшена совместимость с мобильными устройствами
-
-✅ **Улучшенные мета-теги:**
-
-- Добавлены все необходимые apple-touch-icon размеры
-- Улучшена поддержка iOS Safari
-- Добавлены мета-теги для Windows
-- Настроен правильный статус-бар для iOS
-
-✅ **Улучшенный Service Worker:**
-
-- Кэширование всех иконок
-- Улучшенная обработка ошибок
-- Fallback для различных типов ресурсов
-- Лучшая поддержка мобильных устройств
-
-✅ **Улучшенный PWA Manager:**
-
-- Определение мобильного Safari
-- Разные инструкции для разных браузеров
-- Улучшенная проверка установки PWA
-- Поддержка различных режимов отображения
-- **Кнопка установки показывается только на мобильных устройствах**
-- Принудительная проверка возможности установки
-- Специальные инструкции для Android и iOS
-- Диагностическая страница для отладки PWA
-
-## Возможные проблемы и решения
-
-### 1. Кнопка установки не появляется
-
-**Причины:**
-
-- Приложение уже установлено
-- Браузер не поддерживает PWA
-- Не выполнены требования PWA
-- **Вы используете ПК/десктоп (кнопка скрыта для ПК)**
-- **Проблемы с Brave браузером**
-
-**Решение:**
-
-- Проверьте статус на мобильной тестовой странице
-- Убедитесь, что используете HTTPS или localhost
-- Проверьте консоль браузера на ошибки
-- Для iOS Safari используйте инструкции "Добавить на главный экран"
-- **На ПК используйте меню браузера для установки PWA**
-- **Для Brave: проверьте настройки PWA в браузере**
-
-### 1.1. Проблемы с Brave браузером
-
-**Причины:**
-
-- Brave может блокировать PWA по умолчанию
-- Неправильная конфигурация manifest.json
-- Проблемы с Service Worker в Brave
-
-**Решение:**
-
-- Откройте `http://localhost:3000/brave-pwa-test.html` для диагностики
-- В Brave перейдите в **Настройки → Дополнительно → Сайты и разрешения → PWA**
-- Убедитесь, что PWA включены
-- Попробуйте установить через меню браузера (три точки → "Установить приложение")
-- Обновите manifest.json с новыми полями для Brave совместимости
-
-### 2. Service Worker не регистрируется
-
-**Причины:**
-
-- Ошибки в коде SW
-- Проблемы с кэшированием файлов
-
-**Решение:**
-
-- Откройте DevTools → Application → Service Workers
-- Проверьте ошибки в консоли
-- Попробуйте очистить кэш
-
-### 3. Ошибка "Download error or resource isn't a valid image"
-
-**Причины:**
-
-- PNG иконки повреждены или имеют неправильный размер
-- Иконки не являются валидными PNG файлами
-
-**Решение:**
-
-- ✅ **ИСПРАВЛЕНО**: Созданы правильные PNG иконки с помощью pngjs
-- Проверьте, что иконки имеют правильные размеры (192x192, 512x512)
-- Убедитесь, что файлы иконок валидные PNG
-
-### 4. Предупреждение о deprecated meta tag
-
-**Причины:**
-
-- Использование устаревшего `apple-mobile-web-app-capable`
-
-**Решение:**
-
-- ✅ **ИСПРАВЛЕНО**: Добавлен современный `mobile-web-app-capable`
-- Оба тега теперь присутствуют для совместимости
-
-## Отладка
-
-### Консоль браузера
-
-Откройте DevTools (F12) и проверьте консоль на ошибки:
-
-```javascript
-// Проверить статус PWA
-window.debugPWA();
-
-// Проверить Service Worker
-navigator.serviceWorker.getRegistrations().then(console.log);
-
-// Проверить возможность установки
-window.checkInstallability();
-
-// Принудительная попытка установки
-window.forceInstall();
-```
-
-### Диагностическая страница
-
-Используйте `http://localhost:3000/pwa-debug.html` для:
-
-- Детальной диагностики всех требований PWA
-- Проверки загрузки манифеста и Service Worker
-- Отображения информации о браузере и устройстве
-- Просмотра ошибок консоли
-- Тестирования установки PWA
-
-### Lighthouse
-
-Запустите аудит Lighthouse в Chrome DevTools:
-
-1. Откройте DevTools (F12)
-2. Перейдите в Lighthouse
-3. Выберите "Progressive Web App"
-4. Нажмите "Generate report"
-
-## Файлы для проверки
-
-- ✅ `http://localhost:3000/manifest.json` - должен возвращать JSON
-- ✅ `http://localhost:3000/sw.js` - должен возвращать JavaScript
-- ✅ `http://localhost:3000/icons/icon-192x192.png` - должен возвращать PNG
-- ✅ `http://localhost:3000/icons/icon-512x512.png` - должен возвращать PNG
-- ✅ `http://localhost:3000/pwa-debug.html` - диагностическая страница PWA
-- ✅ `http://localhost:3000/mobile-pwa-test.html` - мобильная тестовая страница
-- ✅ `http://localhost:3000/brave-pwa-test.html` - тестовая страница для Brave браузера
-- ✅ `http://localhost:3000/test-pwa.html` - обычная тестовая страница
-
-## Следующие шаги
-
-После успешного тестирования:
-
-1. Удалите тестовую страницу `test-pwa.html`
-2. Настройте HTTPS для продакшена
-3. Добавьте реальные скриншоты в манифест
-4. Создайте качественные иконки с помощью дизайнера
-5. Настройте push-уведомления (опционально)
diff --git a/public/app.js b/public/app.js
index 4dbc509..b0cbf95 100644
--- a/public/app.js
+++ b/public/app.js
@@ -36,6 +36,47 @@ let searchResults = [];
let notesCache = null; // Кэш для заметок
let lastLoadTime = 0; // Время последней загрузки
+// 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", // Загружать изображения за 50px до появления в viewport
+ 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 getFormattedDateTime() {
let now = new Date();
@@ -151,7 +192,10 @@ function renderTags() {
// Добавляем обработчики кликов для тегов
tagsContainer.querySelectorAll(".tag").forEach((tagElement) => {
- tagElement.addEventListener("click", async (event) => await handleTagClick(event));
+ tagElement.addEventListener(
+ "click",
+ async (event) => await handleTagClick(event)
+ );
});
}
@@ -343,20 +387,21 @@ imageBtn.addEventListener("touchend", function (event) {
imageInput.addEventListener("change", function (event) {
const files = Array.from(event.target.files);
let addedCount = 0;
-
- files.forEach(file => {
- if (file.type.startsWith('image/')) {
+
+ files.forEach((file) => {
+ if (file.type.startsWith("image/")) {
// Проверяем размер файла (максимум 10MB)
if (file.size > 10 * 1024 * 1024) {
alert(`Файл "${file.name}" слишком большой. Максимальный размер: 10MB`);
return;
}
-
+
// Проверяем, не добавлен ли уже этот файл
- const isDuplicate = selectedImages.some(existingFile =>
- existingFile.name === file.name && existingFile.size === file.size
+ const isDuplicate = selectedImages.some(
+ (existingFile) =>
+ existingFile.name === file.name && existingFile.size === file.size
);
-
+
if (!isDuplicate) {
selectedImages.push(file);
addedCount++;
@@ -365,7 +410,7 @@ imageInput.addEventListener("change", function (event) {
alert(`Файл "${file.name}" не является изображением`);
}
});
-
+
if (addedCount > 0) {
updateImagePreview();
// Показываем уведомление о добавленных файлах
@@ -375,9 +420,9 @@ imageInput.addEventListener("change", function (event) {
console.log(`Добавлено ${addedCount} изображений`);
}
}
-
+
// Очищаем input для возможности повторного выбора тех же файлов
- event.target.value = '';
+ event.target.value = "";
});
// Обработчик очистки всех изображений
@@ -420,19 +465,20 @@ function updateImagePreview() {
reader.onload = function (e) {
const previewItem = document.createElement("div");
previewItem.className = "image-preview-item";
-
+
// Форматируем размер файла
const fileSize = (file.size / 1024 / 1024).toFixed(2);
- const fileName = file.name.length > 20 ? file.name.substring(0, 20) + '...' : file.name;
-
+ const fileName =
+ file.name.length > 20 ? file.name.substring(0, 20) + "..." : file.name;
+
previewItem.innerHTML = `
-
+
1. Откройте эту страницу на мобильном устройстве
-2. Должен появиться нативный баннер установки браузера
-3. Если баннер не появился, используйте кнопки ниже
-diff --git a/public/profile.js b/public/profile.js index 333b35b..33edbc5 100644 --- a/public/profile.js +++ b/public/profile.js @@ -11,6 +11,48 @@ const newPasswordInput = document.getElementById("newPassword"); const confirmPasswordInput = document.getElementById("confirmPassword"); const changePasswordBtn = document.getElementById("changePasswordBtn"); const messageContainer = document.getElementById("messageContainer"); +const accentColorInput = document.getElementById("accentColor"); + +// 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") { @@ -36,6 +78,14 @@ async function loadProfile() { // Заполняем поля usernameInput.value = user.username || ""; emailInput.value = user.email || ""; + accentColorInput.value = user.accent_color || "#007bff"; + + // Обновляем выбранный цвет в цветовых опциях + updateColorPickerSelection(user.accent_color || "#007bff"); + + // Применяем цветовой акцент пользователя + const accentColor = user.accent_color || "#007bff"; + document.documentElement.style.setProperty("--accent-color", accentColor); // Обрабатываем аватарку if (user.avatar) { @@ -54,6 +104,17 @@ async function loadProfile() { } } +// Функция для обновления выбора цвета в цветовой палитре +function updateColorPickerSelection(selectedColor) { + const colorOptions = document.querySelectorAll(".color-option"); + colorOptions.forEach((option) => { + option.classList.remove("selected"); + if (option.dataset.color === selectedColor) { + option.classList.add("selected"); + } + }); +} + // Обработчик загрузки аватарки avatarInput.addEventListener("change", async function (event) { const file = event.target.files[0]; @@ -139,6 +200,7 @@ deleteAvatarBtn.addEventListener("click", async function () { updateProfileBtn.addEventListener("click", async function () { const username = usernameInput.value.trim(); const email = emailInput.value.trim(); + const accentColor = accentColorInput.value; // Валидация if (!username) { @@ -165,6 +227,7 @@ updateProfileBtn.addEventListener("click", async function () { body: JSON.stringify({ username, email: email || null, + accent_color: accentColor, }), }); @@ -174,6 +237,10 @@ updateProfileBtn.addEventListener("click", async function () { } const result = await response.json(); + + // Применяем новый цветовой акцент + document.documentElement.style.setProperty("--accent-color", accentColor); + showMessage(result.message || "Профиль успешно обновлен", "success"); } catch (error) { console.error("Ошибка обновления профиля:", error); @@ -247,30 +314,30 @@ function isValidEmail(email) { // Функция для проверки аутентификации async function checkAuthentication() { - const isAuthenticated = localStorage.getItem('isAuthenticated'); - - if (isAuthenticated !== 'true') { + 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'); + localStorage.removeItem("isAuthenticated"); + localStorage.removeItem("username"); window.location.href = "/"; return; } - + const authData = await response.json(); if (!authData.authenticated) { // Если сервер говорит, что пользователь не аутентифицирован - localStorage.removeItem('isAuthenticated'); - localStorage.removeItem('username'); + localStorage.removeItem("isAuthenticated"); + localStorage.removeItem("username"); window.location.href = "/"; return; } @@ -285,21 +352,44 @@ async function checkAuthentication() { // Функция для настройки обработчика выхода function setupLogoutHandler() { const logoutForms = document.querySelectorAll('form[action="/logout"]'); - logoutForms.forEach(form => { - form.addEventListener('submit', function(e) { + logoutForms.forEach((form) => { + form.addEventListener("submit", function (e) { // Очищаем localStorage перед выходом - localStorage.removeItem('isAuthenticated'); - localStorage.removeItem('username'); + localStorage.removeItem("isAuthenticated"); + localStorage.removeItem("username"); }); }); } +// Обработчики для цветовой палитры +function setupColorPicker() { + const colorOptions = document.querySelectorAll(".color-option"); + colorOptions.forEach((option) => { + option.addEventListener("click", function () { + const selectedColor = this.dataset.color; + accentColorInput.value = selectedColor; + updateColorPickerSelection(selectedColor); + }); + }); + + // Обработчик для input color + accentColorInput.addEventListener("input", function () { + updateColorPickerSelection(this.value); + }); +} + // Загружаем профиль при загрузке страницы document.addEventListener("DOMContentLoaded", function () { // Проверяем аутентификацию при загрузке страницы checkAuthentication(); loadProfile(); - + + // Инициализируем lazy loading для изображений + initLazyLoading(); + + // Настраиваем цветовую палитру + setupColorPicker(); + // Добавляем обработчик для кнопки выхода setupLogoutHandler(); }); diff --git a/public/pwa-debug.html b/public/pwa-debug.html deleted file mode 100644 index 072d160..0000000 --- a/public/pwa-debug.html +++ /dev/null @@ -1,411 +0,0 @@ - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-