diff --git a/config/services.json b/config/services.json index 64d843f..1606fd4 100644 --- a/config/services.json +++ b/config/services.json @@ -8,66 +8,89 @@ } ], - "Development": [ + "Storage": [ + { + "name": "Nextcloud", + "link": "https://cloud.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/nextcloud-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/nextcloud-dark.svg", + "alt": "Nextcloud" + }, { "name": "Gitea", "link": "https://code.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-dark.svg", "alt": "Gitea", "tags": ["git", "code", "repository"] }, { "name": "OpenGist", "link": "https://snippets.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist-dark.svg", "alt": "OpenGist", "tags": ["gist", "snippets"] }, + { + "name": "Calibre-Web", + "link": "https://books.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre-web-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre-web-dark.svg", + "alt": "Calibre-Web", + "tags": ["books", "read"] + } + ], + + "Utilities": [ + { + "name": "Searxng", + "link": "https://search.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng-dark.svg", + "alt": "Searxng", + "tags": ["search", "privacy", "metasearch"] + }, { "name": "IT-Tools", "link": "https://tools.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools-dark.svg", "alt": "IT-Tools", "tags": ["dev"] }, { "name": "Ollama", "link": "https://ai.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ollama.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ollama-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ollama-dark.svg", "alt": "Ollama", "tags": ["LLM", "AI", "models", "chatbot"] } - ], - - "Entertainment": [ - { - "name": "Calibre-Web", - "link": "https://books.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre.svg", - "alt": "Calibre-Web", - "tags": ["books", "read"] - } ], "Analytics & Monitoring": [ { "name": "Uptime Kuma", "link": "https://status.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma-dark.svg", "alt": "Uptime Kuma", "tags": ["status"] }, { "name": "Umami", "link": "https://analytics.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami-dark.svg", "alt": "Umami", "tags": ["analytics"] }, { "name": "TeslaMate", "link": "https://tesla.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate-dark.svg", "alt": "TeslaMate", "tags": ["car", "tesla"] } @@ -76,63 +99,51 @@ "Infrastructure": [ { "name": "Pi-hole", - "link": "https://pihole.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole.svg", + "link": "http://pi.hole/admin/", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole-dark.svg", "alt": "Pi-hole", "tags": ["dns"] }, { "name": "Ntfy", "link": "https://ntfy.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy-dark.svg", "alt": "Ntfy", "tags": ["notifications"] }, { "name": "Vaultwarden", "link": "https://passwords.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden-dark.svg", "alt": "Vaultwarden", "tags": ["passwords"] }, { "name": "Authentik", "link": "https://auth.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik-dark.svg", "alt": "Authentik", "tags": ["SSO", "Auth", "Authentication"] }, { "name": "Traefik", "link": "https://proxy.justin.deal:8080", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik.svg", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik-dark.svg", "alt": "Traefik", "tags": ["proxy", "reverse-proxy", "load-balancer"] - } - ], - - - "Utilities": [ - { - "name": "Searxng", - "link": "https://search.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng.svg", - "alt": "Searxng", - "tags": ["search", "privacy", "metasearch"] }, { - "name": "Silverbullet", - "link": "https://notes.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/png/silverbullet.png", - "alt": "Silverbullet", - "tags": ["notes", "markdown", "knowledge base"] - }, - { - "name": "Vikunja", - "link": "https://todo.justin.deal", - "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vikunja.svg", - "alt": "Vikunja", - "tags": ["todo", "tasks", "productivity"] + "name": "Syncthing", + "link": "https://sync.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-light.svg", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-dark.svg", + "alt": "Syncthing", + "tags": ["sync", "files"] } ] } diff --git a/public/service-worker.js b/public/service-worker.js index 8447086..ff8606c 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -389,11 +389,17 @@ self.addEventListener('message', event => { cacheNames.map(cacheName => caches.delete(cacheName)) ); }).then(() => { - // Notify client that caches were cleared - event.ports[0].postMessage({ - status: 'success', - message: 'All caches cleared successfully' - }); + // Notify client that caches were cleared, but only if port is available + if (event.ports && event.ports.length > 0) { + try { + event.ports[0].postMessage({ + status: 'success', + message: 'All caches cleared successfully' + }); + } catch (err) { + console.log('Could not post message to client: port may be closed'); + } + } }) ); } diff --git a/src/components/common/ScrollReveal.astro b/src/components/common/ScrollReveal.astro index 900967a..b8699d4 100644 --- a/src/components/common/ScrollReveal.astro +++ b/src/components/common/ScrollReveal.astro @@ -73,6 +73,9 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`; if (!scrollRevealElements.length) return; + // Store observers to properly disconnect them later + const observers = new Map(); + // Check if IntersectionObserver is supported if ('IntersectionObserver' in window) { scrollRevealElements.forEach(element => { @@ -96,6 +99,8 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`; // Unobserve if once is true if (once) { observer.unobserve(entry.target); + // Remove from observers map to allow garbage collection + observers.delete(entry.target); } } else if (!once) { // Remove class if element leaves viewport and once is false @@ -109,9 +114,36 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`; } ); + // Store observer reference for cleanup + observers.set(element, observer); + // Start observing observer.observe(element); }); + + // Cleanup function to disconnect observers when page changes or component unmounts + const cleanup = () => { + observers.forEach((observer, element) => { + observer.unobserve(element); + observer.disconnect(); + }); + observers.clear(); + }; + + // Clean up observers when page is about to unload + window.addEventListener('beforeunload', cleanup); + + // For SPA navigation (if applicable) + document.addEventListener('astro:before-swap', cleanup); + + // Additional cleanup for any framework-specific unmounting + document.addEventListener('astro:after-swap', () => { + // Re-initialize on page navigation for SPAs + const newScrollRevealElements = document.querySelectorAll('.scroll-reveal'); + if (newScrollRevealElements.length) { + // This will be handled by the DOMContentLoaded event in the new page + } + }); } else { // Fallback for browsers that don't support IntersectionObserver scrollRevealElements.forEach(element => { diff --git a/src/components/common/ServiceCard.astro b/src/components/common/ServiceCard.astro index d7e4026..fd41673 100644 --- a/src/components/common/ServiceCard.astro +++ b/src/components/common/ServiceCard.astro @@ -7,7 +7,8 @@ * * ``` @@ -24,17 +25,26 @@ interface Props { href: string; /** - * The URL of the service icon + * The URL of the service icon (light version) */ img: string; + /** + * The URL of the service icon for dark mode (dark version) + * If not provided, falls back to the light version + */ + imgDark?: string; + /** * Alternative text for the service icon */ alt: string; } -const { name, href, img, alt } = Astro.props; +const { name, href, img, imgDark, alt } = Astro.props; + +// Use the provided dark icon or fall back to the light icon +const darkIcon = imgDark || img; // Generate a unique ID for the service card const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`; @@ -50,10 +60,22 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`; >
+ {alt} + + {alt}
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index ca02ffc..99a9713 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -50,10 +50,32 @@ import "../styles/global.css"; navigator.serviceWorker.register('/service-worker.js') .then(registration => { console.log('SW registered: ', registration.scope); + + // Handle communication errors + navigator.serviceWorker.addEventListener('message', event => { + // Process messages from service worker + if (event.data && event.data.type) { + console.log('Message from SW:', event.data.type); + } + }); + + // Handle controller change + navigator.serviceWorker.addEventListener('controllerchange', () => { + console.log('Service worker controller changed'); + }); }) .catch(error => { console.log('SW registration failed: ', error); }); + + // Handle communication errors globally + window.addEventListener('error', event => { + if (event.message && event.message.includes('postMessage')) { + console.log('Caught postMessage error:', event.message); + // Prevent the error from bubbling up + event.preventDefault(); + } + }); }); } @@ -92,13 +114,15 @@ import "../styles/global.css"; rel="stylesheet" /> - + + diff --git a/src/lib/config.ts b/src/lib/config.ts index f1de7fe..f9427f5 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -74,6 +74,7 @@ export interface Service { name: string; link: string; icon: string; + iconDark?: string; alt: string; tags?: string[]; } diff --git a/src/lib/types.ts b/src/lib/types.ts index 11b34cb..e2e575e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -103,10 +103,17 @@ export type Service = { link: string; /** - * The URL to the service icon + * The URL to the service icon (light version) + * Used in dark theme */ icon: string; + /** + * The URL to the dark version of the service icon + * Used in light theme + */ + iconDark?: string; + /** * Alternative text for the service icon */