feat(pwa): app instalável com service worker e shell offline
- manifest.webmanifest: standalone, theme/background color, ícones 192/512 any+maskable+monochrome (PNGs em web/icons) e atalho 'Nova nota' (?new=1) - sw.js: cache versionado (notas-shell-v1); navegação network-first com fallback ao shell (offline), estáticos stale-while-revalidate, /api/* e SSE sempre passthrough; activate limpa caches e notifica janelas - app.js: registro do SW, beforeinstallprompt com item 'Instalar Aplicativo…' no menu Ajuda (escondido em standalone/após instalar), toast de atualização, aviso offline/online, deep-link ?new=1 - index.html: meta tags theme-color/mobile-web-app/apple-touch-icon/manifest - server: staticHandler com MIME application/manifest+json e Cache-Control no-cache para /sw.js; embed inclui manifest, sw e ícones - README: seção PWA + orientação de serving (Go atual e equivalente Rust)
This commit is contained in:
1 parent
63fd024f68
commit
18d33ed141
12 files changed
+243
-3
No files matched your search
@@ -1792,6 +1792,15 @@
|
||||
'toggle-gutter': () => { prefs.gutter = !prefs.gutter; persistPrefs(); applyPrefs(); },
|
||||
'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…');
|
||||
},
|
||||
'toggle-edit': () => setMode('edit'),
|
||||
'toggle-wysiwyg': () => setMode('wysiwyg'),
|
||||
'toggle-preview': () => setMode('preview'),
|
||||
@@ -1938,6 +1947,44 @@
|
||||
E.authorInput.addEventListener('change', () =>
|
||||
localStorage.setItem('notas_author', E.authorInput.value.trim()));
|
||||
|
||||
/* ------------------------------------------------------------ PWA */
|
||||
|
||||
const isStandalone = () =>
|
||||
window.matchMedia('(display-mode: standalone)').matches ||
|
||||
window.matchMedia('(display-mode: window-controls-overlay)').matches ||
|
||||
window.navigator.standalone === true;
|
||||
|
||||
let deferredPrompt = null;
|
||||
const miInstall = document.querySelector('.menu-dropdown [data-action="install"]');
|
||||
|
||||
function refreshInstallUI() {
|
||||
if (miInstall) miInstall.classList.toggle('hidden', isStandalone() || !deferredPrompt);
|
||||
}
|
||||
|
||||
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 */ });
|
||||
navigator.serviceWorker.addEventListener('message', (e) => {
|
||||
if (e.data === 'notas:updated') toast('App atualizado para a nova versão');
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('offline', () => toast('Sem conexão — o app abre offline; salvar precisa do servidor'));
|
||||
window.addEventListener('online', () => { toast('Conexão restabelecida'); checkFreshness(); });
|
||||
|
||||
const wantsNew = new URLSearchParams(location.search).get('new') === '1';
|
||||
if (wantsNew) history.replaceState(null, '', location.pathname);
|
||||
|
||||
/* ------------------------------------------------------------ boot */
|
||||
|
||||
async function boot() {
|
||||
@@ -1946,6 +1993,8 @@
|
||||
await restoreTabs();
|
||||
updateStatus();
|
||||
state.booted = true;
|
||||
refreshInstallUI();
|
||||
if (wantsNew) await newDraftTab();
|
||||
connectEvents();
|
||||
sendPresence();
|
||||
setInterval(sendPresence, 10000); // heartbeat de presença
|
||||
|
||||
Reference in new issue
Block a user