- Erstellen-Button in der Sidebar: SVG-Path war asymmetrisch (M19 13h-6v9...) — Vertikalbalken hing nach unten heraus. Jetzt Standard-Plus mit gleich langen Armen. - Service Worker holt index.html und version.js ab sofort per Network-First — neue Releases greifen damit beim nächsten Seiten-Reload, ohne dass der SW manuell deregistriert werden muss. Statics bleiben Cache-First für Offline-Tauglichkeit; auf Aktivierung wird der alte Cache komplett gelöscht. Version v9 → v10.
110 lines
3.2 KiB
JavaScript
110 lines
3.2 KiB
JavaScript
// Calendarr Service Worker
|
|
// Cache-first for static assets, network-first for /api/* (graceful offline)
|
|
|
|
const CACHE_VERSION = 'calendarr-v10';
|
|
const STATIC_ASSETS = [
|
|
'/',
|
|
'/index.html',
|
|
'/manifest.json',
|
|
'/static/css/app.css',
|
|
'/static/favicon.svg',
|
|
'/static/js/app.js',
|
|
'/static/js/api.js',
|
|
'/static/js/calendar.js',
|
|
'/static/js/color-picker.js',
|
|
'/static/js/date-picker.js',
|
|
'/static/js/i18n.js',
|
|
'/static/js/utils.js',
|
|
'/static/js/version.js',
|
|
'/static/js/views/agenda.js',
|
|
'/static/js/views/month.js',
|
|
'/static/js/views/quarter.js',
|
|
'/static/js/views/week.js',
|
|
'/icons/icon-192.png',
|
|
'/icons/icon-512.png',
|
|
'/icons/icon.svg',
|
|
];
|
|
|
|
self.addEventListener('install', event => {
|
|
event.waitUntil(
|
|
caches.open(CACHE_VERSION).then(cache =>
|
|
// Use addAll with a fallback so a single missing file doesn't abort install
|
|
Promise.all(
|
|
STATIC_ASSETS.map(url =>
|
|
cache.add(url).catch(err => console.warn('[SW] skip', url, err))
|
|
)
|
|
)
|
|
).then(() => self.skipWaiting())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('activate', event => {
|
|
event.waitUntil(
|
|
caches.keys().then(keys =>
|
|
Promise.all(keys.filter(k => k !== CACHE_VERSION).map(k => caches.delete(k)))
|
|
).then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', event => {
|
|
const req = event.request;
|
|
if (req.method !== 'GET') return;
|
|
|
|
const url = new URL(req.url);
|
|
|
|
// Network-first for API routes — fail silently if offline
|
|
if (url.pathname.startsWith('/api/')) {
|
|
event.respondWith(
|
|
fetch(req).catch(() =>
|
|
new Response(JSON.stringify({ offline: true }), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Network-first for navigation (HTML) and the version-defining files —
|
|
// ensures users always get the freshest entry point so new releases
|
|
// take effect on the next reload without a manual SW unregister.
|
|
const isHtml = req.mode === 'navigate'
|
|
|| url.pathname === '/'
|
|
|| url.pathname === '/index.html';
|
|
const isVersionFile = url.pathname === '/static/js/version.js';
|
|
|
|
if (isHtml || isVersionFile) {
|
|
event.respondWith(
|
|
fetch(req).then(resp => {
|
|
if (resp && resp.status === 200) {
|
|
const clone = resp.clone();
|
|
caches.open(CACHE_VERSION).then(c => c.put(req, clone)).catch(() => {});
|
|
}
|
|
return resp;
|
|
}).catch(() =>
|
|
caches.match(req).then(c => c || caches.match('/index.html'))
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Cache-first for everything else (static)
|
|
event.respondWith(
|
|
caches.match(req).then(cached => {
|
|
if (cached) return cached;
|
|
return fetch(req).then(resp => {
|
|
// Only cache successful, basic-origin responses
|
|
if (resp && resp.status === 200 && resp.type === 'basic') {
|
|
const clone = resp.clone();
|
|
caches.open(CACHE_VERSION).then(c => c.put(req, clone)).catch(() => {});
|
|
}
|
|
return resp;
|
|
}).catch(() => {
|
|
// Offline fallback for navigation requests
|
|
if (req.mode === 'navigate') return caches.match('/index.html');
|
|
return new Response('', { status: 503 });
|
|
});
|
|
})
|
|
);
|
|
});
|