diff --git a/PWA/package-lock.json b/PWA/package-lock.json index 4e833c4..4e570c0 100644 --- a/PWA/package-lock.json +++ b/PWA/package-lock.json @@ -2363,9 +2363,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2380,9 +2377,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2397,9 +2391,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2414,9 +2405,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2431,9 +2419,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2448,9 +2433,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2465,9 +2447,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2482,9 +2461,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2499,9 +2475,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2516,9 +2489,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2533,9 +2503,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2550,9 +2517,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2567,9 +2531,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2793,9 +2754,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2813,9 +2771,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2833,9 +2788,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2853,9 +2805,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5015,9 +4964,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5039,9 +4985,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5063,9 +5006,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5087,9 +5027,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/PWA/public/favicon.svg b/PWA/public/favicon.svg index 7b4341b..30aa78f 100644 --- a/PWA/public/favicon.svg +++ b/PWA/public/favicon.svg @@ -1,4 +1,11 @@ - - - E - + + + + + + + + + + + \ No newline at end of file diff --git a/PWA/public/icon-192.png b/PWA/public/icon-192.png index 50e43b8..cb6e15a 100644 Binary files a/PWA/public/icon-192.png and b/PWA/public/icon-192.png differ diff --git a/PWA/public/icon-512.png b/PWA/public/icon-512.png index 5220d0f..b24e40e 100644 Binary files a/PWA/public/icon-512.png and b/PWA/public/icon-512.png differ diff --git a/PWA/public/sw-push.js b/PWA/public/sw-push.js new file mode 100644 index 0000000..8dc1afb --- /dev/null +++ b/PWA/public/sw-push.js @@ -0,0 +1,26 @@ +self.addEventListener('push', function (event) { + var data = {}; + try { data = event.data.json(); } catch (e) { data = { title: '沈晏', body: event.data ? event.data.text() : '' }; } + event.waitUntil( + self.registration.showNotification(data.title || '沈晏', { + body: data.body || '', + icon: '/icon-192.png', + badge: '/icon-192.png', + vibrate: [200, 100, 200], + tag: 'shenyan-push', + renotify: true, + }) + ); +}); + +self.addEventListener('notificationclick', function (event) { + event.notification.close(); + event.waitUntil( + clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (list) { + for (var i = 0; i < list.length; i++) { + if (list[i].url && 'focus' in list[i]) return list[i].focus(); + } + if (clients.openWindow) return clients.openWindow('/'); + }) + ); +}); diff --git a/PWA/public/weather-cloudy.svg b/PWA/public/weather-cloudy.svg new file mode 100644 index 0000000..e707729 --- /dev/null +++ b/PWA/public/weather-cloudy.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/PWA/public/weather-fog.svg b/PWA/public/weather-fog.svg new file mode 100644 index 0000000..a26ed71 --- /dev/null +++ b/PWA/public/weather-fog.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PWA/public/weather-partly-cloudy.svg b/PWA/public/weather-partly-cloudy.svg new file mode 100644 index 0000000..5c70072 --- /dev/null +++ b/PWA/public/weather-partly-cloudy.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/PWA/public/weather-rain.svg b/PWA/public/weather-rain.svg new file mode 100644 index 0000000..64c9c7b --- /dev/null +++ b/PWA/public/weather-rain.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/PWA/public/weather-snow.svg b/PWA/public/weather-snow.svg new file mode 100644 index 0000000..95cc2cc --- /dev/null +++ b/PWA/public/weather-snow.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/PWA/public/weather-storm.svg b/PWA/public/weather-storm.svg new file mode 100644 index 0000000..5dfc9ad --- /dev/null +++ b/PWA/public/weather-storm.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PWA/public/weather-sunny.svg b/PWA/public/weather-sunny.svg new file mode 100644 index 0000000..30aa78f --- /dev/null +++ b/PWA/public/weather-sunny.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/PWA/src/App.tsx b/PWA/src/App.tsx index 01b31fb..ebae70f 100644 --- a/PWA/src/App.tsx +++ b/PWA/src/App.tsx @@ -46,6 +46,29 @@ async function saveRemote(msgs: Message[]) { } catch { /* silent */ } } +async function setupPush() { + if (!('serviceWorker' in navigator) || !('PushManager' in window)) return + try { + const reg = await navigator.serviceWorker.ready + const existing = await reg.pushManager.getSubscription() + if (existing) return // already subscribed + + const res = await fetch('/push/vapid-public-key') + const { key } = await res.json() + + const sub = await reg.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: key, + }) + + await fetch('/push/subscribe', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(sub), + }) + } catch { /* silent — user may have denied */ } +} + export default function App() { const [messages, setMessages] = useState(loadLocal) const [loading, setLoading] = useState(false) @@ -59,14 +82,14 @@ export default function App() { setMessages(remote) nextId.current = Math.max(...remote.map(m => m.id)) + 1 } else { - const local = loadLocal() - if (local.length > 0) { - nextId.current = Math.max(...local.map(m => m.id)) + 1 - saveRemote(local) + if (messages.length > 0) { + nextId.current = Math.max(...messages.map(m => m.id)) + 1 + saveRemote(messages) } } setSynced(true) })() + setupPush() }, []) useEffect(() => { @@ -105,14 +128,9 @@ export default function App() { } } - const handleClear = () => { - setMessages([]) - localStorage.removeItem(STORAGE_KEY) - } - return (
-
- 沈晏 - {messages.length > 0 && ( - - )} -
diff --git a/PWA/src/components/ChatArea.tsx b/PWA/src/components/ChatArea.tsx index a420dc4..67d7516 100644 --- a/PWA/src/components/ChatArea.tsx +++ b/PWA/src/components/ChatArea.tsx @@ -2,22 +2,24 @@ import { useRef, useEffect } from 'react' import type { Message } from '../App' export default function ChatArea({ messages }: { messages: Message[] }) { - const bottom = useRef(null) + const container = useRef(null) useEffect(() => { - bottom.current?.scrollIntoView({ behavior: 'smooth' }) + if (container.current) { + container.current.scrollTop = container.current.scrollHeight + } }, [messages]) return ( -
-
+
+
{messages.map((m) => (
))} -
+
) diff --git a/PWA/src/components/InputArea.tsx b/PWA/src/components/InputArea.tsx index aac5424..9ffaf10 100644 --- a/PWA/src/components/InputArea.tsx +++ b/PWA/src/components/InputArea.tsx @@ -12,11 +12,28 @@ export default function InputArea({ onSend, disabled }: { onSend: (text: string) return (
+