diff --git a/public/app.js b/public/app.js index f9850e9..c18a97c 100644 --- a/public/app.js +++ b/public/app.js @@ -31,8 +31,24 @@ document.addEventListener('DOMContentLoaded', () => { setupMobileAddLink(); setupLayoutToggle(); applyLayout(currentLayout); + registerServiceWorker(); }); +// Register service worker for PWA +function registerServiceWorker() { + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js') + .then((registration) => { + console.log('Service Worker registered successfully:', registration.scope); + }) + .catch((error) => { + console.log('Service Worker registration failed:', error); + }); + }); + } +} + // Event listeners function setupEventListeners() { linkForm.addEventListener('submit', handleAddLink); diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000..f6a451b Binary files /dev/null and b/public/icon-192.png differ diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000..aa0cb97 Binary files /dev/null and b/public/icon-512.png differ diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..55e6374 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/index.html b/public/index.html index f30d0b0..660ca68 100644 --- a/public/index.html +++ b/public/index.html @@ -2,8 +2,15 @@ - + + + + + + LinkDing - Your Link Collection + + diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..65a9c66 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,25 @@ +{ + "name": "LinkDing", + "short_name": "LinkDing", + "description": "Your Link Collection", + "start_url": "/", + "display": "fullscreen", + "background_color": "#0f172a", + "theme_color": "#6366f1", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} + diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..262b65e --- /dev/null +++ b/public/sw.js @@ -0,0 +1,82 @@ +const CACHE_NAME = 'linkding-v1'; +const urlsToCache = [ + '/', + '/index.html', + '/styles.css', + '/app.js', + '/manifest.json', + '/icon-192.png', + '/icon-512.png' +]; + +// Install event - cache resources +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME) + .then((cache) => { + console.log('Opened cache'); + return cache.addAll(urlsToCache); + }) + .catch((error) => { + console.error('Cache install failed:', error); + }) + ); + self.skipWaiting(); +}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log('Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }) + ); + return self.clients.claim(); +}); + +// Fetch event - serve from cache, fallback to network +self.addEventListener('fetch', (event) => { + // API requests - always fetch from network, don't cache + if (event.request.url.includes('/api/')) { + event.respondWith(fetch(event.request)); + return; + } + + // Static assets - cache first, then network + event.respondWith( + caches.match(event.request) + .then((response) => { + // Return cached version or fetch from network + return response || fetch(event.request).then((response) => { + // Don't cache non-successful responses + if (!response || response.status !== 200 || response.type !== 'basic') { + return response; + } + + // Clone the response + const responseToCache = response.clone(); + + caches.open(CACHE_NAME) + .then((cache) => { + cache.put(event.request, responseToCache); + }); + + return response; + }); + }) + .catch(() => { + // If both cache and network fail, return offline page if available + if (event.request.destination === 'document') { + return caches.match('/index.html'); + } + }) + ); +}); +