diff --git a/src/config/urls.py b/src/config/urls.py index 8787d0b..4aac360 100644 --- a/src/config/urls.py +++ b/src/config/urls.py @@ -1,10 +1,23 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.http import HttpResponse, HttpResponseNotFound from django.urls import include, path, re_path from django.views.static import serve + +def service_worker(_request): + """Serve the service worker at the root scope.""" + sw_path = settings.BASE_DIR / "static" / "js" / "service-worker.js" + try: + with sw_path.open() as f: + return HttpResponse(f.read(), content_type="application/javascript") + except (FileNotFoundError, PermissionError): + return HttpResponseNotFound("Service worker not found") + + urlpatterns = [ + path("service-worker.js", service_worker, name="service_worker"), path("", include("core.urls")), path("accounts/", include("accounts.urls")), path("accounts/", include("django.contrib.auth.urls")), diff --git a/src/static/images/icons/icon-192.png b/src/static/images/icons/icon-192.png new file mode 100644 index 0000000..8ec27e9 Binary files /dev/null and b/src/static/images/icons/icon-192.png differ diff --git a/src/static/images/icons/icon-512.png b/src/static/images/icons/icon-512.png new file mode 100644 index 0000000..792b196 Binary files /dev/null and b/src/static/images/icons/icon-512.png differ diff --git a/src/static/js/base.js b/src/static/js/base.js index 38f5491..c2f8adf 100644 --- a/src/static/js/base.js +++ b/src/static/js/base.js @@ -137,9 +137,23 @@ function initToastMessages() { }); } +// SERVICE WORKER REGISTRATION +function registerServiceWorker() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/service-worker.js') + .then((registration) => { + console.log('Service Worker registered with scope:', registration.scope); + }) + .catch((error) => { + console.error('Service Worker registration failed:', error); + }); + } +} + // INITIALIZE ALL FEATURES ON DOM READY document.addEventListener('DOMContentLoaded', function() { initThemeSwitcher(); cleanUrlParameters(); initToastMessages(); + registerServiceWorker(); }); diff --git a/src/static/js/service-worker.js b/src/static/js/service-worker.js new file mode 100644 index 0000000..f82188e --- /dev/null +++ b/src/static/js/service-worker.js @@ -0,0 +1,90 @@ +const CACHE_NAME = 'datakult-v1'; +const STATIC_ASSETS = [ + '/', + '/static/manifest.json', + '/static/images/bookshelf.png', + '/static/images/icons/icon-192.png', + '/static/images/icons/icon-512.png', +]; + +// Install event: cache static assets +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(STATIC_ASSETS); + }) + ); + self.skipWaiting(); +}); + +// Activate event: clean up old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames + .filter((name) => name !== CACHE_NAME) + .map((name) => caches.delete(name)) + ); + }) + ); + self.clients.claim(); +}); + +// Fetch event: network-first strategy for HTML, cache-first for static assets +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // Skip non-GET requests + if (request.method !== 'GET') { + return; + } + + // Skip cross-origin requests + if (url.origin !== location.origin) { + return; + } + + // For HTML pages: network-first (try network, fallback to cache) + if (request.headers.get('Accept')?.includes('text/html')) { + event.respondWith( + fetch(request) + .then((response) => { + if (response.ok) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(request, responseClone); + }); + } + return response; + }) + .catch(() => { + // Fallback to cache if network fails + return caches.match(request); + }) + ); + return; + } + + // For static assets: cache-first (try cache, fallback to network) + if (url.pathname.startsWith('/static/')) { + event.respondWith( + caches.match(request).then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; + } + return fetch(request).then((response) => { + if (response.ok) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(request, responseClone); + }); + } + return response; + }); + }) + ); + return; + } +}); diff --git a/src/static/manifest.json b/src/static/manifest.json new file mode 100644 index 0000000..978ac89 --- /dev/null +++ b/src/static/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Datakult", + "short_name": "Datakult", + "description": "Application de gestion de médias", + "start_url": "/", + "display": "standalone", + "background_color": "#1d232a", + "theme_color": "#1d232a", + "icons": [ + { + "src": "/static/images/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/static/images/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + } + ] +} diff --git a/src/templates/base/base.html b/src/templates/base/base.html index ea125c3..5c06d44 100644 --- a/src/templates/base/base.html +++ b/src/templates/base/base.html @@ -18,6 +18,8 @@ + + {% tailwind_css %} {% htmx_script %} {% block extra_css %} diff --git a/src/templates/base/media_index.html b/src/templates/base/media_index.html index a50daf2..ea7491b 100644 --- a/src/templates/base/media_index.html +++ b/src/templates/base/media_index.html @@ -35,7 +35,7 @@