diff --git a/README.md b/README.md index 52bc3aa..3f3ebe3 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,21 @@ do navegador e atalho de área de trabalho "Nova nota" (`/?new=1`). - **Offline UX**: shell + interface funcionam sem rede; ações que precisam do servidor mostram toast claro ("Sem conexão — o app abre offline; salvar precisa do servidor"). +### Compatibilidade de instalação por navegador + +| Navegador | Service Worker / offline | Instalação | Botão "Instalar Aplicativo…" | +|---|---|---|---| +| Chrome/Edge/Brave/Vivaldi | ✅ | `beforeinstallprompt` (prompt nativo) | ✅ dispara o prompt | +| Firefox Android | ✅ | menu **⋮ → Instalar** (tela inicial) | ✅ abre instruções | +| Firefox Windows 143+ | ✅ | **fixar na barra de tarefas** pela barra de endereço | ✅ abre instruções | +| Firefox Linux/macOS | ✅ (app funciona pleno em aba) | sem fluxo nativo (só extensão de terceiros) | ✅ abre instruções honestas | +| Safari (macOS/iOS) | ✅ | manual: Compartilhar → *Adicionar ao Dock/Tela de Início* | escondido (sem evento) | + +O código usa a checagem de propriedade `'onbeforeinstallprompt' in window` (o evento **nunca** +dispara em Firefox/Safari — MDN), então no Firefox o item do menu sempre aparece e mostra o +passo a passo da plataforma; em standalone (app instalado) o item se esconde sozinho via +`display-mode: standalone`, que o Firefox também reporta. + ### Servindo os arquivos (backend Go atual) Já implementado em `internal/api/server.go` (`staticHandler`): diff --git a/web/js/app.js b/web/js/app.js index a4c7d42..cb9ecd9 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -1793,13 +1793,17 @@ 'toggle-wrap': () => { prefs.wrap = !prefs.wrap; persistPrefs(); applyPrefs(); }, 'prefs': openPrefs, 'install': async () => { - if (!deferredPrompt) { toast('Use o ícone de instalação da barra de endereço.'); return; } - const p = deferredPrompt; - deferredPrompt = null; - refreshInstallUI(); - p.prompt(); - const { outcome } = await p.userChoice; - if (outcome === 'accepted') toast('Instalando o Notas como aplicativo…'); + if (deferredPrompt) { + const p = deferredPrompt; + deferredPrompt = null; + refreshInstallUI(); + p.prompt(); + const { outcome } = await p.userChoice; + if (outcome === 'accepted') toast('Instalando o Notas como aplicativo…'); + return; + } + if (isFirefox) { firefoxInstallHelp(); return; } + toast('Use o ícone de instalação da barra de endereço.'); }, 'toggle-edit': () => setMode('edit'), 'toggle-wysiwyg': () => setMode('wysiwyg'), @@ -1954,23 +1958,55 @@ window.matchMedia('(display-mode: window-controls-overlay)').matches || window.navigator.standalone === true; + // beforeinstallprompt só existe em Chromium (Firefox/Safari nunca disparam o + // evento nem 'appinstalled') — checagem de propriedade, não de evento. + const canBIP = 'onbeforeinstallprompt' in window; + const ua = navigator.userAgent; + const isFirefox = /Firefox\//.test(ua); + const isAndroid = /Android/.test(ua); + const isWindows = /Windows/.test(ua); + let deferredPrompt = null; const miInstall = document.querySelector('.menu-dropdown [data-action="install"]'); function refreshInstallUI() { - if (miInstall) miInstall.classList.toggle('hidden', isStandalone() || !deferredPrompt); + if (!miInstall) return; + const show = !isStandalone() && (canBIP ? !!deferredPrompt : isFirefox); + miInstall.classList.toggle('hidden', !show); } - window.addEventListener('beforeinstallprompt', (e) => { - e.preventDefault(); // prompt próprio via menu Ajuda - deferredPrompt = e; - refreshInstallUI(); - }); - window.addEventListener('appinstalled', () => { - deferredPrompt = null; - refreshInstallUI(); - toast('Notas instalado ✔ procure pelo menu do sistema'); - }); + function firefoxInstallHelp() { + const steps = isAndroid + ? '

No Firefox para Android:

  1. Toque no menu ⋮ da barra de endereço;
  2. ' + + '
  3. Toque em Instalar → Adicionar automaticamente;
  4. ' + + '
  5. O Notas entra na tela inicial e abre em modo aplicativo (standalone).
' + : isWindows + ? '

No Firefox para Windows (143+):

  1. Clique no ícone da barra de endereço ' + + '(Adicionar aba à barra de tarefas);
  2. ' + + '
  3. O site fica fixado na barra de tarefas e abre em janela simplificada de app.
' + : '

O Firefox para Linux/macOS não oferece instalação nativa de PWAs (a Mozilla ' + + 'prevê isso via extensão de terceiros).

'; + modal({ + title: 'Instalar o Notas no Firefox', + bodyHTML: steps + + '

Em qualquer caso o app funciona offline (instalado ou em aba): a interface é ' + + 'carregada pelo Service Worker mesmo sem servidor. As alterações só sincronizam com o ' + + 'servidor acessível.

', + }); + } + + if (canBIP) { + window.addEventListener('beforeinstallprompt', (e) => { + e.preventDefault(); // prompt próprio via menu Ajuda + deferredPrompt = e; + refreshInstallUI(); + }); + window.addEventListener('appinstalled', () => { + deferredPrompt = null; + refreshInstallUI(); + toast('Notas instalado ✔ procure pelo menu do sistema'); + }); + } if ('serviceWorker' in navigator && location.protocol.startsWith('http')) { navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(() => { /* sem SW: app continua */ }); diff --git a/web/sw.js b/web/sw.js index 1662cc6..71a56c4 100644 --- a/web/sw.js +++ b/web/sw.js @@ -1,7 +1,7 @@ /* Service Worker do Notas — caches versionados. * BUMP de CACHE em todo deploy que mude shell/assets (o activate limpa os antigos). */ -const CACHE = 'notas-shell-v1'; +const CACHE = 'notas-shell-v2'; const SHELL = [ '/', '/css/app.css',