网络暂时走丢了
+已经读过的文章仍可从浏览器历史或返回按钮打开。网络恢复后,再来继续探索吧。
+ + 返回首页 +diff --git a/public/images/apple-touch-icon.png b/public/images/apple-touch-icon.png new file mode 100644 index 0000000..54af06e Binary files /dev/null and b/public/images/apple-touch-icon.png differ diff --git a/public/images/pwa-192x192.png b/public/images/pwa-192x192.png new file mode 100644 index 0000000..213c484 Binary files /dev/null and b/public/images/pwa-192x192.png differ diff --git a/public/images/pwa-512x512.png b/public/images/pwa-512x512.png new file mode 100644 index 0000000..e0ed3c3 Binary files /dev/null and b/public/images/pwa-512x512.png differ diff --git a/public/images/pwa-maskable-512x512.png b/public/images/pwa-maskable-512x512.png new file mode 100644 index 0000000..dfdbcb9 Binary files /dev/null and b/public/images/pwa-maskable-512x512.png differ diff --git a/public/js/pwa.js b/public/js/pwa.js new file mode 100644 index 0000000..465f9cc --- /dev/null +++ b/public/js/pwa.js @@ -0,0 +1,104 @@ +(() => { + if (!('serviceWorker' in navigator)) return; + + const toast = document.querySelector('[data-pwa-toast]'); + const title = toast?.querySelector('[data-pwa-title]'); + const message = toast?.querySelector('[data-pwa-message]'); + const primary = toast?.querySelector('[data-pwa-primary]'); + const close = toast?.querySelector('[data-pwa-close]'); + let installPrompt = null; + let waitingWorker = null; + let toastMode = ''; + let refreshing = false; + + const showToast = (mode, heading, detail, actionLabel) => { + if (!toast || !title || !message || !primary) return; + toastMode = mode; + title.textContent = heading; + message.textContent = detail; + primary.textContent = actionLabel; + primary.hidden = !actionLabel; + toast.hidden = false; + }; + + const hideToast = () => { + if (toast) toast.hidden = true; + }; + + const installDismissedRecently = () => { + try { + const dismissedAt = Number(localStorage.getItem('pwa-install-dismissed-at')); + return dismissedAt > 0 && Date.now() - dismissedAt < 30 * 86400000; + } catch { + return false; + } + }; + + window.addEventListener('beforeinstallprompt', (event) => { + event.preventDefault(); + installPrompt = event; + if (!installDismissedRecently()) { + window.setTimeout(() => { + if (installPrompt) showToast('install', '安装 Bi\'s Blog', '添加到桌面,像应用一样打开并阅读缓存过的文章。', '立即安装'); + }, 2500); + } + }); + + window.addEventListener('appinstalled', () => { + installPrompt = null; + showToast('ready', '安装完成', '博客已经添加到你的设备。', '知道了'); + }); + + primary?.addEventListener('click', async () => { + if (toastMode === 'install' && installPrompt) { + await installPrompt.prompt(); + await installPrompt.userChoice; + installPrompt = null; + hideToast(); + return; + } + + if (toastMode === 'update' && waitingWorker) { + refreshing = true; + waitingWorker.postMessage({ type: 'SKIP_WAITING' }); + return; + } + + hideToast(); + }); + + close?.addEventListener('click', () => { + if (toastMode === 'install') { + try { localStorage.setItem('pwa-install-dismissed-at', String(Date.now())); } catch {} + } + hideToast(); + }); + + navigator.serviceWorker.addEventListener('controllerchange', () => { + if (refreshing) window.location.reload(); + }); + + navigator.serviceWorker.register('/sw.js', { scope: '/' }).then((registration) => { + if (registration.waiting && navigator.serviceWorker.controller) { + waitingWorker = registration.waiting; + showToast('update', '发现新版本', '刷新后即可使用博客的最新内容和功能。', '立即更新'); + } + + registration.addEventListener('updatefound', () => { + const worker = registration.installing; + worker?.addEventListener('statechange', () => { + if (worker.state === 'installed' && navigator.serviceWorker.controller) { + waitingWorker = worker; + showToast('update', '发现新版本', '刷新后即可使用博客的最新内容和功能。', '立即更新'); + } + }); + }); + }).catch((error) => { + console.warn('PWA registration failed:', error); + }); + + const params = new URLSearchParams(window.location.search); + if (params.get('action') === 'search') { + window.setTimeout(() => document.querySelector('.search-typesense-trigger')?.click(), 350); + } +})(); diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..40337ed --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,46 @@ +{ + "id": "/", + "name": "Bi's Blog", + "short_name": "Bi's Blog", + "description": "好奇心转化为代码的空间。", + "lang": "zh-CN", + "start_url": "/", + "scope": "/", + "display": "standalone", + "display_override": ["window-controls-overlay", "standalone", "minimal-ui"], + "theme_color": "#0f766e", + "background_color": "#f7f5ef", + "categories": ["blog", "education", "technology"], + "icons": [ + { + "src": "/images/pwa-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/images/pwa-512x512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "/images/pwa-maskable-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "shortcuts": [ + { + "name": "搜索文章", + "short_name": "搜索", + "url": "/?action=search", + "icons": [{ "src": "/images/pwa-192x192.png", "sizes": "192x192" }] + }, + { + "name": "随机文章", + "short_name": "随机文章", + "url": "/go/", + "icons": [{ "src": "/images/pwa-192x192.png", "sizes": "192x192" }] + } + ] +} diff --git a/public/offline.html b/public/offline.html new file mode 100644 index 0000000..0fac7f4 --- /dev/null +++ b/public/offline.html @@ -0,0 +1,29 @@ + + +
+ + + +已经读过的文章仍可从浏览器历史或返回按钮打开。网络恢复后,再来继续探索吧。
+ + 返回首页 +