✨ Улучшена поддержка PWA и мобильных устройств
- Обновлены инструкции по тестированию PWA для мобильных и десктопных устройств. - Добавлены новые мета-теги и улучшены иконки для поддержки iOS и Windows. - Оптимизирован Service Worker для кэширования и обработки ошибок. - Реализована кнопка установки, отображающаяся только на мобильных устройствах, с различными инструкциями для разных браузеров. - Обновлен manifest.json с добавлением категорий и скриншотов.
This commit is contained in:
parent
fbc2b5259c
commit
95401328c4
@ -22,25 +22,30 @@
|
||||
|
||||
## Как протестировать
|
||||
|
||||
### 1. Откройте тестовую страницу
|
||||
### 1. Откройте тестовую страницу для мобильных устройств
|
||||
```
|
||||
http://localhost:3000/mobile-pwa-test.html
|
||||
```
|
||||
|
||||
### 2. Откройте обычную тестовую страницу
|
||||
```
|
||||
http://localhost:3000/test-pwa.html
|
||||
```
|
||||
|
||||
### 2. Проверьте требования PWA
|
||||
Нажмите кнопку "Проверить статус PWA" - все пункты должны быть зелеными:
|
||||
### 3. Проверьте требования PWA
|
||||
На мобильной тестовой странице автоматически проверяются все требования:
|
||||
- ✅ HTTPS или localhost
|
||||
- ✅ Service Worker
|
||||
- ✅ Manifest
|
||||
- ✅ Иконки
|
||||
- ❌ Уже установлено (должно быть красным, если не установлено)
|
||||
|
||||
### 3. Установка приложения
|
||||
### 4. Установка приложения
|
||||
- Если все проверки пройдены, появится кнопка "Установить приложение"
|
||||
- Нажмите на неё для установки PWA
|
||||
- Следуйте инструкциям браузера
|
||||
- Следуйте инструкциям браузера или используйте инструкции на странице
|
||||
|
||||
### 4. Проверка в разных браузерах
|
||||
### 5. Проверка в разных браузерах
|
||||
|
||||
#### Chrome/Edge:
|
||||
- Откройте DevTools (F12)
|
||||
@ -59,6 +64,39 @@ http://localhost:3000/test-pwa.html
|
||||
- Выберите "На экран Домой"
|
||||
- Приложение установится как 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
|
||||
- Поддержка различных режимов отображения
|
||||
- **Кнопка установки показывается только на мобильных устройствах**
|
||||
|
||||
## Возможные проблемы и решения
|
||||
|
||||
### 1. Кнопка установки не появляется
|
||||
@ -66,11 +104,14 @@ http://localhost:3000/test-pwa.html
|
||||
- Приложение уже установлено
|
||||
- Браузер не поддерживает PWA
|
||||
- Не выполнены требования PWA
|
||||
- **Вы используете ПК/десктоп (кнопка скрыта для ПК)**
|
||||
|
||||
**Решение:**
|
||||
- Проверьте статус на тестовой странице
|
||||
- Проверьте статус на мобильной тестовой странице
|
||||
- Убедитесь, что используете HTTPS или localhost
|
||||
- Проверьте консоль браузера на ошибки
|
||||
- Для iOS Safari используйте инструкции "Добавить на главный экран"
|
||||
- **На ПК используйте меню браузера для установки PWA**
|
||||
|
||||
### 2. Service Worker не регистрируется
|
||||
**Причины:**
|
||||
@ -125,6 +166,8 @@ navigator.serviceWorker.getRegistrations().then(console.log);
|
||||
- ✅ `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/mobile-pwa-test.html` - мобильная тестовая страница
|
||||
- ✅ `http://localhost:3000/test-pwa.html` - обычная тестовая страница
|
||||
|
||||
## Следующие шаги
|
||||
|
||||
|
||||
@ -10,15 +10,27 @@
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
<meta name="apple-touch-fullscreen" content="yes" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
|
||||
<meta name="application-name" content="NoteJS" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
@ -109,25 +121,32 @@
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
|
||||
// Показываем кнопку установки
|
||||
const installButton = document.createElement('button');
|
||||
installButton.textContent = 'Установить приложение';
|
||||
installButton.className = 'btnSave';
|
||||
installButton.style.marginTop = '10px';
|
||||
installButton.style.width = '100%';
|
||||
// Проверяем, является ли устройство мобильным
|
||||
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;
|
||||
|
||||
installButton.addEventListener('click', () => {
|
||||
deferredPrompt.prompt();
|
||||
deferredPrompt.userChoice.then((choiceResult) => {
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
console.log('Пользователь установил приложение');
|
||||
}
|
||||
deferredPrompt = null;
|
||||
installButton.remove();
|
||||
// Показываем кнопку установки только на мобильных устройствах
|
||||
if (isMobile) {
|
||||
const installButton = document.createElement('button');
|
||||
installButton.textContent = 'Установить приложение';
|
||||
installButton.className = 'btnSave';
|
||||
installButton.style.marginTop = '10px';
|
||||
installButton.style.width = '100%';
|
||||
|
||||
installButton.addEventListener('click', () => {
|
||||
deferredPrompt.prompt();
|
||||
deferredPrompt.userChoice.then((choiceResult) => {
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
console.log('Пользователь установил приложение');
|
||||
}
|
||||
deferredPrompt = null;
|
||||
installButton.remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('.auth-link').appendChild(installButton);
|
||||
|
||||
document.querySelector('.auth-link').appendChild(installButton);
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка успешной установки
|
||||
|
||||
@ -9,18 +9,75 @@
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"lang": "ru",
|
||||
"categories": ["productivity", "utilities"],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"form_factor": "narrow"
|
||||
}
|
||||
],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
415
public/mobile-pwa-test.html
Normal file
415
public/mobile-pwa-test.html
Normal file
@ -0,0 +1,415 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Тест PWA для мобильных устройств - NoteJS</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="Тестирование PWA на мобильных устройствах" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
<meta name="apple-touch-fullscreen" content="yes" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
|
||||
<meta name="application-name" content="NoteJS" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
|
||||
<style>
|
||||
.test-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.test-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.test-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status.pass {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status.fail {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.status.warning {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.install-button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.install-button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.install-button:disabled {
|
||||
background: #6c757d;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
background: #e7f3ff;
|
||||
border: 1px solid #b3d9ff;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.instructions h4 {
|
||||
margin-top: 0;
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.instructions ol {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.instructions li {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1>📱 Тест PWA для мобильных устройств</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Проверка требований PWA</h3>
|
||||
<div id="pwa-checks">
|
||||
<div class="test-item">
|
||||
<span>HTTPS или localhost</span>
|
||||
<span class="status" id="https-check">Проверка...</span>
|
||||
</div>
|
||||
<div class="test-item">
|
||||
<span>Service Worker</span>
|
||||
<span class="status" id="sw-check">Проверка...</span>
|
||||
</div>
|
||||
<div class="test-item">
|
||||
<span>Manifest</span>
|
||||
<span class="status" id="manifest-check">Проверка...</span>
|
||||
</div>
|
||||
<div class="test-item">
|
||||
<span>Иконки</span>
|
||||
<span class="status" id="icons-check">Проверка...</span>
|
||||
</div>
|
||||
<div class="test-item">
|
||||
<span>Уже установлено</span>
|
||||
<span class="status" id="installed-check">Проверка...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Установка приложения</h3>
|
||||
<div id="device-info" style="margin-bottom: 10px; padding: 10px; background: #e7f3ff; border-radius: 4px; font-size: 14px;"></div>
|
||||
<button id="install-button" class="install-button" disabled>
|
||||
Установить приложение
|
||||
</button>
|
||||
<div id="install-status"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Инструкции по установке</h3>
|
||||
<div class="instructions">
|
||||
<h4>Android Chrome/Edge:</h4>
|
||||
<ol>
|
||||
<li>Откройте сайт в Chrome или Edge</li>
|
||||
<li>Нажмите кнопку "Установить приложение" выше</li>
|
||||
<li>Или нажмите меню (⋮) → "Установить приложение"</li>
|
||||
<li>Следуйте инструкциям браузера</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<h4>iOS Safari:</h4>
|
||||
<ol>
|
||||
<li>Откройте сайт в Safari</li>
|
||||
<li>Нажмите кнопку "Поделиться" (□↗)</li>
|
||||
<li>Выберите "На экран Домой"</li>
|
||||
<li>Нажмите "Добавить"</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<h4>Другие браузеры:</h4>
|
||||
<ol>
|
||||
<li>Найдите опцию "Установить" в меню браузера</li>
|
||||
<li>Или используйте "Добавить на главный экран"</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Отладочная информация</h3>
|
||||
<button onclick="showDebugInfo()" class="install-button">Показать отладочную информацию</button>
|
||||
<div id="debug-info" class="debug-info" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Действия</h3>
|
||||
<button onclick="clearCache()" class="install-button">Очистить кэш</button>
|
||||
<button onclick="forceUpdate()" class="install-button">Принудительное обновление</button>
|
||||
<button onclick="location.reload()" class="install-button">Перезагрузить страницу</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let deferredPrompt;
|
||||
|
||||
// Проверка требований PWA
|
||||
function checkPWARequirements() {
|
||||
// HTTPS или localhost
|
||||
const isSecure = location.protocol === 'https:' || location.hostname === 'localhost';
|
||||
document.getElementById('https-check').textContent = isSecure ? '✅ Да' : '❌ Нет';
|
||||
document.getElementById('https-check').className = `status ${isSecure ? 'pass' : 'fail'}`;
|
||||
|
||||
// Service Worker
|
||||
const hasSW = 'serviceWorker' in navigator;
|
||||
document.getElementById('sw-check').textContent = hasSW ? '✅ Да' : '❌ Нет';
|
||||
document.getElementById('sw-check').className = `status ${hasSW ? 'pass' : 'fail'}`;
|
||||
|
||||
// Manifest
|
||||
const hasManifest = document.querySelector('link[rel="manifest"]') !== null;
|
||||
document.getElementById('manifest-check').textContent = hasManifest ? '✅ Да' : '❌ Нет';
|
||||
document.getElementById('manifest-check').className = `status ${hasManifest ? 'pass' : 'fail'}`;
|
||||
|
||||
// Иконки
|
||||
const hasIcons = document.querySelector('link[rel="icon"]') !== null;
|
||||
document.getElementById('icons-check').textContent = hasIcons ? '✅ Да' : '❌ Нет';
|
||||
document.getElementById('icons-check').className = `status ${hasIcons ? 'pass' : 'fail'}`;
|
||||
|
||||
// Уже установлено
|
||||
const isInstalled = window.matchMedia('(display-mode: standalone)').matches ||
|
||||
window.navigator.standalone === true;
|
||||
document.getElementById('installed-check').textContent = isInstalled ? '✅ Да' : '❌ Нет';
|
||||
document.getElementById('installed-check').className = `status ${isInstalled ? 'warning' : 'pass'}`;
|
||||
}
|
||||
|
||||
// Регистрация Service Worker
|
||||
function registerServiceWorker() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then((registration) => {
|
||||
console.log('SW зарегистрирован успешно:', registration.scope);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Ошибка регистрации SW:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка установки
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
console.log('beforeinstallprompt событие получено');
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
|
||||
// Проверяем, является ли устройство мобильным
|
||||
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;
|
||||
|
||||
const installButton = document.getElementById('install-button');
|
||||
if (isMobile) {
|
||||
installButton.disabled = false;
|
||||
installButton.textContent = '📱 Установить приложение';
|
||||
} else {
|
||||
installButton.disabled = true;
|
||||
installButton.textContent = '💻 Установка доступна через меню браузера';
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка успешной установки
|
||||
window.addEventListener('appinstalled', () => {
|
||||
console.log('PWA установлено успешно');
|
||||
document.getElementById('install-status').innerHTML =
|
||||
'<div style="color: green; margin-top: 10px;">✅ Приложение успешно установлено!</div>';
|
||||
|
||||
const installButton = document.getElementById('install-button');
|
||||
installButton.disabled = true;
|
||||
installButton.textContent = 'Приложение установлено';
|
||||
});
|
||||
|
||||
// Установка приложения
|
||||
document.getElementById('install-button').addEventListener('click', () => {
|
||||
if (deferredPrompt) {
|
||||
deferredPrompt.prompt();
|
||||
deferredPrompt.userChoice.then((choiceResult) => {
|
||||
console.log('Результат установки:', choiceResult.outcome);
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
document.getElementById('install-status').innerHTML =
|
||||
'<div style="color: green; margin-top: 10px;">✅ Пользователь принял установку</div>';
|
||||
} else {
|
||||
document.getElementById('install-status').innerHTML =
|
||||
'<div style="color: orange; margin-top: 10px;">⚠️ Пользователь отклонил установку</div>';
|
||||
}
|
||||
deferredPrompt = null;
|
||||
});
|
||||
} else {
|
||||
document.getElementById('install-status').innerHTML =
|
||||
'<div style="color: red; margin-top: 10px;">❌ Установка недоступна. Попробуйте использовать меню браузера.</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// Показать отладочную информацию
|
||||
function showDebugInfo() {
|
||||
const debugInfo = document.getElementById('debug-info');
|
||||
const info = {
|
||||
userAgent: navigator.userAgent,
|
||||
platform: navigator.platform,
|
||||
language: navigator.language,
|
||||
onLine: navigator.onLine,
|
||||
cookieEnabled: navigator.cookieEnabled,
|
||||
displayMode: window.matchMedia('(display-mode: standalone)').matches ? 'standalone' : 'browser',
|
||||
standalone: window.navigator.standalone,
|
||||
hasServiceWorker: 'serviceWorker' in navigator,
|
||||
hasDeferredPrompt: deferredPrompt !== null,
|
||||
location: {
|
||||
href: location.href,
|
||||
protocol: location.protocol,
|
||||
hostname: location.hostname,
|
||||
port: location.port
|
||||
}
|
||||
};
|
||||
|
||||
debugInfo.textContent = JSON.stringify(info, null, 2);
|
||||
debugInfo.style.display = debugInfo.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Очистка кэша
|
||||
async function clearCache() {
|
||||
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
|
||||
try {
|
||||
navigator.serviceWorker.controller.postMessage({
|
||||
type: 'CLEAR_ALL_CACHE'
|
||||
});
|
||||
alert('Запрос на очистку кэша отправлен');
|
||||
} catch (error) {
|
||||
alert('Ошибка при очистке кэша: ' + error.message);
|
||||
}
|
||||
} else {
|
||||
alert('Service Worker не доступен');
|
||||
}
|
||||
}
|
||||
|
||||
// Принудительное обновление
|
||||
async function forceUpdate() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
if (registration) {
|
||||
await registration.update();
|
||||
alert('Проверка обновлений завершена');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка при проверке обновлений: ' + error.message);
|
||||
}
|
||||
} else {
|
||||
alert('Service Worker не поддерживается');
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка типа устройства
|
||||
function checkDeviceType() {
|
||||
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;
|
||||
|
||||
const deviceInfo = document.getElementById('device-info');
|
||||
if (isMobile) {
|
||||
deviceInfo.innerHTML = '📱 <strong>Мобильное устройство</strong> - кнопка установки доступна';
|
||||
deviceInfo.style.background = '#d4edda';
|
||||
deviceInfo.style.color = '#155724';
|
||||
} else {
|
||||
deviceInfo.innerHTML = '💻 <strong>ПК/Десктоп</strong> - кнопка установки скрыта (PWA доступно через меню браузера)';
|
||||
deviceInfo.style.background = '#fff3cd';
|
||||
deviceInfo.style.color = '#856404';
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
checkDeviceType();
|
||||
checkPWARequirements();
|
||||
registerServiceWorker();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -10,14 +10,27 @@
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
<meta name="apple-touch-fullscreen" content="yes" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
|
||||
<meta name="application-name" content="NoteJS" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
|
||||
@ -80,14 +80,47 @@ class PWAManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем кнопку только на мобильных устройствах
|
||||
if (!this.isMobileDevice()) {
|
||||
console.log('Кнопка установки скрыта для ПК версии');
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, поддерживает ли браузер установку PWA
|
||||
if (!this.deferredPrompt && !this.isMobileSafari()) {
|
||||
console.log('Установка PWA не поддерживается в этом браузере');
|
||||
return;
|
||||
}
|
||||
|
||||
const installButton = this.createInstallButton();
|
||||
this.addInstallButtonToPage(installButton);
|
||||
}
|
||||
|
||||
// Проверка на мобильное устройство
|
||||
isMobileDevice() {
|
||||
const ua = navigator.userAgent;
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua) ||
|
||||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
|
||||
window.matchMedia('(max-width: 768px)').matches;
|
||||
}
|
||||
|
||||
// Проверка на мобильный Safari
|
||||
isMobileSafari() {
|
||||
const ua = navigator.userAgent;
|
||||
return /iPad|iPhone|iPod/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS|OPiOS|mercury/.test(ua);
|
||||
}
|
||||
|
||||
// Создание кнопки установки
|
||||
createInstallButton() {
|
||||
const installButton = document.createElement('button');
|
||||
installButton.textContent = '📱 Установить приложение';
|
||||
|
||||
// Разный текст для разных браузеров
|
||||
if (this.isMobileSafari()) {
|
||||
installButton.textContent = '📱 Добавить на главный экран';
|
||||
} else {
|
||||
installButton.textContent = '📱 Установить приложение';
|
||||
}
|
||||
|
||||
installButton.className = 'btnSave';
|
||||
installButton.style.marginTop = '10px';
|
||||
installButton.style.width = '100%';
|
||||
@ -132,6 +165,13 @@ class PWAManager {
|
||||
// Установка приложения
|
||||
installApp() {
|
||||
console.log('Попытка установки приложения');
|
||||
|
||||
if (this.isMobileSafari()) {
|
||||
// Для iOS Safari показываем инструкции
|
||||
this.showSafariInstructions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.deferredPrompt) {
|
||||
this.deferredPrompt.prompt();
|
||||
this.deferredPrompt.userChoice.then((choiceResult) => {
|
||||
@ -144,9 +184,32 @@ class PWAManager {
|
||||
});
|
||||
} else {
|
||||
console.log('deferredPrompt не доступен');
|
||||
this.showManualInstallInstructions();
|
||||
}
|
||||
}
|
||||
|
||||
// Показать инструкции для Safari
|
||||
showSafariInstructions() {
|
||||
const instructions = `
|
||||
Для установки приложения на iOS:
|
||||
1. Нажмите кнопку "Поделиться" (□↗) внизу экрана
|
||||
2. Выберите "На экран Домой"
|
||||
3. Нажмите "Добавить"
|
||||
`;
|
||||
alert(instructions);
|
||||
}
|
||||
|
||||
// Показать инструкции для ручной установки
|
||||
showManualInstallInstructions() {
|
||||
const instructions = `
|
||||
Для установки приложения:
|
||||
1. Откройте меню браузера (⋮ или ☰)
|
||||
2. Найдите "Установить приложение" или "Добавить на главный экран"
|
||||
3. Следуйте инструкциям браузера
|
||||
`;
|
||||
alert(instructions);
|
||||
}
|
||||
|
||||
// Удаление кнопки установки
|
||||
removeInstallButton() {
|
||||
const installButton = document.getElementById('pwa-install-button');
|
||||
@ -167,7 +230,9 @@ class PWAManager {
|
||||
// Проверка статуса PWA
|
||||
isPWAInstalled() {
|
||||
return window.matchMedia('(display-mode: standalone)').matches ||
|
||||
window.navigator.standalone === true;
|
||||
window.navigator.standalone === true ||
|
||||
document.referrer.includes('android-app://') ||
|
||||
window.matchMedia('(display-mode: fullscreen)').matches;
|
||||
}
|
||||
|
||||
// Получение информации о PWA
|
||||
@ -177,7 +242,12 @@ class PWAManager {
|
||||
isOnline: navigator.onLine,
|
||||
hasServiceWorker: 'serviceWorker' in navigator,
|
||||
userAgent: navigator.userAgent,
|
||||
hasDeferredPrompt: this.deferredPrompt !== null
|
||||
hasDeferredPrompt: this.deferredPrompt !== null,
|
||||
isMobileDevice: this.isMobileDevice(),
|
||||
isMobileSafari: this.isMobileSafari(),
|
||||
platform: navigator.platform,
|
||||
language: navigator.language,
|
||||
displayMode: window.matchMedia('(display-mode: standalone)').matches ? 'standalone' : 'browser'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -10,14 +10,27 @@
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
<meta name="apple-touch-fullscreen" content="yes" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
|
||||
<meta name="application-name" content="NoteJS" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
|
||||
33
public/sw.js
33
public/sw.js
@ -1,5 +1,5 @@
|
||||
// Service Worker для NoteJS
|
||||
const APP_VERSION = '1.0.2';
|
||||
const APP_VERSION = '1.0.3';
|
||||
const CACHE_NAME = `notejs-v${APP_VERSION}`;
|
||||
const STATIC_CACHE_NAME = `notejs-static-v${APP_VERSION}`;
|
||||
|
||||
@ -9,8 +9,16 @@ const STATIC_FILES = [
|
||||
'/index.html',
|
||||
'/style.css',
|
||||
'/manifest.json',
|
||||
'/icons/icon-72x72.png',
|
||||
'/icons/icon-96x96.png',
|
||||
'/icons/icon-128x128.png',
|
||||
'/icons/icon-144x144.png',
|
||||
'/icons/icon-152x152.png',
|
||||
'/icons/icon-192x192.png',
|
||||
'/icons/icon-512x512.png'
|
||||
'/icons/icon-384x384.png',
|
||||
'/icons/icon-512x512.png',
|
||||
'/icon.svg',
|
||||
'/logo.svg'
|
||||
];
|
||||
|
||||
// Установка Service Worker
|
||||
@ -71,6 +79,11 @@ self.addEventListener('fetch', (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Пропускаем запросы к внешним ресурсам
|
||||
if (url.origin !== location.origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Обрабатываем только GET запросы
|
||||
if (request.method === 'GET') {
|
||||
event.respondWith(
|
||||
@ -112,6 +125,22 @@ async function handleRequest(request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback для иконок
|
||||
if (request.url.includes('/icons/')) {
|
||||
const fallbackIcon = await caches.match('/icons/icon-192x192.png');
|
||||
if (fallbackIcon) {
|
||||
return fallbackIcon;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback для CSS
|
||||
if (request.url.includes('/style.css')) {
|
||||
const fallbackCSS = await caches.match('/style.css');
|
||||
if (fallbackCSS) {
|
||||
return fallbackCSS;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user