diff --git a/cmd/librenotes/web/public/app.html b/cmd/librenotes/web/public/app.html index 808d815..7c500d3 100644 --- a/cmd/librenotes/web/public/app.html +++ b/cmd/librenotes/web/public/app.html @@ -5,6 +5,8 @@ Notes — librenotes + + @@ -24,5 +26,6 @@ + diff --git a/cmd/librenotes/web/public/icons/icon-192.png b/cmd/librenotes/web/public/icons/icon-192.png new file mode 100644 index 0000000..cfab37c Binary files /dev/null and b/cmd/librenotes/web/public/icons/icon-192.png differ diff --git a/cmd/librenotes/web/public/icons/icon-512.png b/cmd/librenotes/web/public/icons/icon-512.png new file mode 100644 index 0000000..350fbaa Binary files /dev/null and b/cmd/librenotes/web/public/icons/icon-512.png differ diff --git a/cmd/librenotes/web/public/icons/icon-maskable.png b/cmd/librenotes/web/public/icons/icon-maskable.png new file mode 100644 index 0000000..ca71935 Binary files /dev/null and b/cmd/librenotes/web/public/icons/icon-maskable.png differ diff --git a/cmd/librenotes/web/public/index.html b/cmd/librenotes/web/public/index.html index 53b0d2e..a744be5 100644 --- a/cmd/librenotes/web/public/index.html +++ b/cmd/librenotes/web/public/index.html @@ -10,6 +10,8 @@ + + @@ -34,6 +36,7 @@

Sign in with email Self-host it +

@@ -69,5 +72,6 @@

MIT licensed. Built on Notesium.

+ diff --git a/cmd/librenotes/web/public/login.html b/cmd/librenotes/web/public/login.html index ba4ed43..3b747a6 100644 --- a/cmd/librenotes/web/public/login.html +++ b/cmd/librenotes/web/public/login.html @@ -5,6 +5,8 @@ Sign in — librenotes + + @@ -34,5 +36,6 @@ + diff --git a/cmd/librenotes/web/public/manifest.webmanifest b/cmd/librenotes/web/public/manifest.webmanifest new file mode 100644 index 0000000..67bcb3e --- /dev/null +++ b/cmd/librenotes/web/public/manifest.webmanifest @@ -0,0 +1,32 @@ +{ + "name": "librenotes", + "short_name": "librenotes", + "description": "Open-source multi-tenant notes with bi-directional links.", + "start_url": "/app.html", + "scope": "/", + "display": "standalone", + "orientation": "any", + "background_color": "#ffffff", + "theme_color": "#2563eb", + "categories": ["productivity", "utilities"], + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/cmd/librenotes/web/public/pwa.js b/cmd/librenotes/web/public/pwa.js new file mode 100644 index 0000000..3874c9d --- /dev/null +++ b/cmd/librenotes/web/public/pwa.js @@ -0,0 +1,43 @@ +// pwa.js — service worker registration and install prompt. +// +// Loaded on every page. Registers the SW from the root scope so +// it controls the entire site, and surfaces an install button on +// browsers that fire beforeinstallprompt (Chrome / Edge / Android). + +(function () { + "use strict"; + + if ("serviceWorker" in navigator) { + window.addEventListener("load", function () { + navigator.serviceWorker.register("/sw.js", { scope: "/" }) + .catch(function (err) { + console.warn("librenotes: service worker registration failed", err); + }); + }); + } + + let deferredPrompt = null; + + window.addEventListener("beforeinstallprompt", function (ev) { + ev.preventDefault(); + deferredPrompt = ev; + const btn = document.getElementById("install-btn"); + if (btn) { + btn.hidden = false; + btn.addEventListener("click", function () { + if (!deferredPrompt) return; + deferredPrompt.prompt(); + deferredPrompt.userChoice.finally(function () { + deferredPrompt = null; + btn.hidden = true; + }); + }, { once: true }); + } + }); + + window.addEventListener("appinstalled", function () { + const btn = document.getElementById("install-btn"); + if (btn) btn.hidden = true; + deferredPrompt = null; + }); +})(); diff --git a/cmd/librenotes/web/public/sw.js b/cmd/librenotes/web/public/sw.js new file mode 100644 index 0000000..59848cc --- /dev/null +++ b/cmd/librenotes/web/public/sw.js @@ -0,0 +1,80 @@ +// sw.js — librenotes service worker. +// +// Strategy: +// - App shell (HTML/CSS/JS in PRECACHE): cache-first. +// This is what makes the page render offline. +// - API calls (/api/*, /auth/*): network-first. +// We never serve stale auth or stale notes from cache. If the +// network is down, the request fails and the app handles it. +// - Other static assets (icons, manifest): cache-first with +// versioned cache name so a deploy of a new SW evicts old. + +const VERSION = "v1"; +const CACHE = "librenotes-shell-" + VERSION; + +const PRECACHE = [ + "/", + "/index.html", + "/login.html", + "/verify.html", + "/app.html", + "/style.css", + "/auth-client.js", + "/login.js", + "/verify.js", + "/app.js", + "/manifest.webmanifest", + "/favicon.svg", +]; + +self.addEventListener("install", function (ev) { + ev.waitUntil( + caches.open(CACHE).then(function (cache) { + return cache.addAll(PRECACHE); + }) + ); + self.skipWaiting(); +}); + +self.addEventListener("activate", function (ev) { + ev.waitUntil( + caches.keys().then(function (keys) { + return Promise.all( + keys.filter(function (k) { return k !== CACHE; }) + .map(function (k) { return caches.delete(k); }) + ); + }).then(function () { return self.clients.claim(); }) + ); +}); + +self.addEventListener("fetch", function (ev) { + const req = ev.request; + if (req.method !== "GET") return; + + const url = new URL(req.url); + if (url.origin !== self.location.origin) return; + + // Network-first for API and auth endpoints. + if (url.pathname.startsWith("/api/") || url.pathname.startsWith("/auth/")) { + ev.respondWith( + fetch(req).catch(function () { + return new Response("offline", { status: 503, statusText: "offline" }); + }) + ); + return; + } + + // Cache-first for everything else (the app shell). + ev.respondWith( + caches.match(req).then(function (cached) { + if (cached) return cached; + return fetch(req).then(function (resp) { + if (resp && resp.status === 200 && resp.type === "basic") { + const copy = resp.clone(); + caches.open(CACHE).then(function (cache) { cache.put(req, copy); }); + } + return resp; + }); + }) + ); +}); diff --git a/cmd/librenotes/web/public/verify.html b/cmd/librenotes/web/public/verify.html index 832ffe7..1842aad 100644 --- a/cmd/librenotes/web/public/verify.html +++ b/cmd/librenotes/web/public/verify.html @@ -5,6 +5,8 @@ Signing in — librenotes + + @@ -24,5 +26,6 @@ +