From efc3c4c777c23a922c2905d10c796f6b13a6e548 Mon Sep 17 00:00:00 2001 From: Fovway Date: Mon, 20 Oct 2025 09:50:26 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=D0=A3=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B1=D0=B8=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлены обработчики для предотвращения дублирования изображений и проверки размера файлов при загрузке (максимум 10MB). - Реализованы уведомления о добавленных изображениях и улучшен интерфейс для мобильных устройств с индикаторами загрузки и сохранения. - Оптимизированы стили для мобильных устройств, включая улучшения для кнопок и элементов управления. --- MOBILE-UPLOAD-TESTING.md | 92 ++++++ public/app.js | 181 ++++++++++- public/style.css | 42 ++- public/test-mobile-upload.html | 576 +++++++++++++++++++++++++++++++++ 4 files changed, 882 insertions(+), 9 deletions(-) create mode 100644 MOBILE-UPLOAD-TESTING.md create mode 100644 public/test-mobile-upload.html diff --git a/MOBILE-UPLOAD-TESTING.md b/MOBILE-UPLOAD-TESTING.md new file mode 100644 index 0000000..0f7d1fc --- /dev/null +++ b/MOBILE-UPLOAD-TESTING.md @@ -0,0 +1,92 @@ +# 📱 Тестирование загрузки изображений на мобильных устройствах + +## Проблема +Пользователи не могли загружать картинки в заметки с мобильных телефонов. + +## Внесенные исправления + +### 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/public/app.js b/public/app.js index 9eeb3ed..4dbc509 100644 --- a/public/app.js +++ b/public/app.js @@ -326,19 +326,58 @@ linkBtn.addEventListener("click", function () { }); // Обработчик для кнопки загрузки изображений -imageBtn.addEventListener("click", function () { +imageBtn.addEventListener("click", function (event) { + event.preventDefault(); + event.stopPropagation(); + imageInput.click(); +}); + +// Дополнительный обработчик для touch событий на мобильных устройствах +imageBtn.addEventListener("touchend", function (event) { + event.preventDefault(); + event.stopPropagation(); imageInput.click(); }); // Обработчик выбора файлов imageInput.addEventListener("change", function (event) { const files = Array.from(event.target.files); + let addedCount = 0; + files.forEach(file => { if (file.type.startsWith('image/')) { - selectedImages.push(file); + // Проверяем размер файла (максимум 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 + ); + + if (!isDuplicate) { + selectedImages.push(file); + addedCount++; + } + } else { + alert(`Файл "${file.name}" не является изображением`); } }); - updateImagePreview(); + + if (addedCount > 0) { + updateImagePreview(); + // Показываем уведомление о добавленных файлах + if (addedCount === 1) { + console.log(`Добавлено 1 изображение`); + } else { + console.log(`Добавлено ${addedCount} изображений`); + } + } + + // Очищаем input для возможности повторного выбора тех же файлов + event.target.value = ''; }); // Обработчик очистки всех изображений @@ -382,21 +421,41 @@ function updateImagePreview() { 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; + previewItem.innerHTML = ` Preview - -
${file.name}
+ +
${fileName}
${fileSize} MB
`; imagePreviewList.appendChild(previewItem); // Обработчик удаления изображения const removeBtn = previewItem.querySelector(".remove-image-btn"); - removeBtn.addEventListener("click", function () { + removeBtn.addEventListener("click", function (event) { + event.preventDefault(); + event.stopPropagation(); + selectedImages.splice(index, 1); + updateImagePreview(); + }); + + // Дополнительный обработчик для touch событий + removeBtn.addEventListener("touchend", function (event) { + event.preventDefault(); + event.stopPropagation(); selectedImages.splice(index, 1); updateImagePreview(); }); }; + + reader.onerror = function() { + console.error('Ошибка чтения файла:', file.name); + alert(`Ошибка чтения файла: ${file.name}`); + }; + reader.readAsDataURL(file); }); } @@ -425,6 +484,35 @@ async function uploadImages(noteId) { }); try { + // Показываем индикатор загрузки для мобильных устройств + const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + (navigator.maxTouchPoints && navigator.maxTouchPoints > 2) || + window.matchMedia('(max-width: 768px)').matches; + + if (isMobile) { + // Создаем простое уведомление о загрузке + const loadingDiv = document.createElement('div'); + loadingDiv.id = 'mobile-upload-loading'; + loadingDiv.style.cssText = ` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 20px; + border-radius: 10px; + z-index: 10000; + font-size: 16px; + text-align: center; + `; + loadingDiv.innerHTML = ` +
📤 Загрузка изображений...
+
${selectedImages.length} файл(ов)
+ `; + document.body.appendChild(loadingDiv); + } + const response = await fetch(`/api/notes/${noteId}/images`, { method: "POST", body: formData, @@ -435,9 +523,25 @@ async function uploadImages(noteId) { } const result = await response.json(); + + // Удаляем индикатор загрузки + const loadingDiv = document.getElementById('mobile-upload-loading'); + if (loadingDiv) { + loadingDiv.remove(); + } + return result.images || []; } catch (error) { console.error("Ошибка загрузки изображений:", error); + + // Удаляем индикатор загрузки в случае ошибки + const loadingDiv = document.getElementById('mobile-upload-loading'); + if (loadingDiv) { + loadingDiv.remove(); + } + + // Показываем ошибку пользователю + alert(`Ошибка загрузки изображений: ${error.message}`); return []; } } @@ -1127,6 +1231,35 @@ async function saveNote() { try { const { date, time } = getFormattedDateTime(); + // Показываем индикатор сохранения для мобильных устройств + const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + (navigator.maxTouchPoints && navigator.maxTouchPoints > 2) || + window.matchMedia('(max-width: 768px)').matches; + + let savingIndicator = null; + if (isMobile) { + savingIndicator = document.createElement('div'); + savingIndicator.id = 'mobile-saving-indicator'; + savingIndicator.style.cssText = ` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 20px; + border-radius: 10px; + z-index: 10000; + font-size: 16px; + text-align: center; + `; + savingIndicator.innerHTML = ` +
💾 Сохранение заметки...
+ ${selectedImages.length > 0 ? `
+ ${selectedImages.length} изображений
` : ''} + `; + document.body.appendChild(savingIndicator); + } + const response = await fetch("/api/notes", { method: "POST", headers: { @@ -1151,6 +1284,11 @@ async function saveNote() { await uploadImages(noteId); } + // Удаляем индикатор сохранения + if (savingIndicator) { + savingIndicator.remove(); + } + // Очищаем поле ввода и изображения, перезагружаем заметки noteInput.value = ""; noteInput.style.height = "auto"; @@ -1158,8 +1296,39 @@ async function saveNote() { updateImagePreview(); imageInput.value = ""; await loadNotes(true); + + // Показываем уведомление об успешном сохранении + if (isMobile) { + const successDiv = document.createElement('div'); + successDiv.style.cssText = ` + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + background: #28a745; + color: white; + padding: 10px 20px; + border-radius: 5px; + z-index: 10000; + font-size: 14px; + `; + successDiv.textContent = '✅ Заметка сохранена!'; + document.body.appendChild(successDiv); + + setTimeout(() => { + successDiv.remove(); + }, 3000); + } + } catch (error) { console.error("Ошибка:", error); + + // Удаляем индикатор сохранения в случае ошибки + const savingIndicator = document.getElementById('mobile-saving-indicator'); + if (savingIndicator) { + savingIndicator.remove(); + } + alert("Ошибка сохранения заметки"); } } diff --git a/public/style.css b/public/style.css index 19c7900..c7e70f5 100644 --- a/public/style.css +++ b/public/style.css @@ -546,6 +546,15 @@ textarea:focus { background-color: #f0f0f0; border-radius: 5px; font-size: 14px; + /* Улучшения для мобильных устройств */ + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + user-select: none; + -webkit-user-select: none; + min-height: 44px; /* Минимальная высота для touch */ + display: inline-flex; + align-items: center; + justify-content: center; } .markdown-buttons .btnMarkdown:hover { @@ -1306,6 +1315,9 @@ textarea:focus { background: #f8f9fa; border: 2px dashed #dee2e6; border-radius: 8px; + /* Улучшения для мобильных устройств */ + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; } .image-preview-header { @@ -1361,14 +1373,19 @@ textarea:focus { color: white; border: none; border-radius: 50%; - width: 20px; - height: 20px; + width: 24px; /* Увеличиваем размер для touch */ + height: 24px; /* Увеличиваем размер для touch */ cursor: pointer; - font-size: 12px; + font-size: 14px; /* Увеличиваем размер шрифта */ display: flex; align-items: center; justify-content: center; transition: background-color 0.3s ease; + /* Улучшения для мобильных устройств */ + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + user-select: none; + -webkit-user-select: none; } .image-preview-item .remove-image-btn:hover { @@ -1515,6 +1532,25 @@ textarea:focus { height: 80px; } + /* Улучшения для мобильных устройств */ + .markdown-buttons .btnMarkdown { + min-height: 48px; /* Увеличиваем высоту для touch */ + padding: 8px 12px; + margin: 2px; + } + + .image-preview-item .remove-image-btn { + width: 28px; /* Еще больше для мобильных */ + height: 28px; + font-size: 16px; + } + + .clear-images-btn { + min-height: 44px; /* Минимальная высота для touch */ + padding: 8px 16px; + font-size: 14px; + } + .note-image { width: 120px; height: 120px; diff --git a/public/test-mobile-upload.html b/public/test-mobile-upload.html new file mode 100644 index 0000000..aa48d15 --- /dev/null +++ b/public/test-mobile-upload.html @@ -0,0 +1,576 @@ + + + + + + Тест загрузки изображений на мобильных - NoteJS + + + + + + + + + + + + + + + + + + + + + + +
+

📱 Тест загрузки изображений на мобильных

+ +
+ Определение устройства... +
+ +
+

Тест загрузки файлов

+ +
+
📷
+
Нажмите для выбора изображений
+
или перетащите файлы сюда
+ +
+ +
+ + + +
+ +
+

Результаты тестов

+
+
+ +
+

Отладочная информация

+ + +
+
+ + + +