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');
+ }
+ })
+ );
+});
+