diff --git a/package.json b/package.json index b11d319..5d48c36 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sycamore_whisper_front", "private": true, - "version": "1.0.0", + "version": "1.0.1", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index 6057462..6c354a5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -// 你好,感谢你愿意看源代码,但是悄悄告诉你,代码其实是AI写的所以质量很差喵。抱歉呜呜呜😭。 +// 你好,感谢你愿意看源代码,但是悄悄告诉你,代码其实是AI写的,所以质量很差喵。抱歉呜呜呜😭。 import React, { useState, useEffect, useRef, useCallback } from 'react'; import { FluentProvider, webLightTheme, webDarkTheme, tokens } from '@fluentui/react-components'; @@ -36,6 +36,8 @@ function App() { const lastRefreshAtRef = useRef(0); const REFRESH_COOLDOWN_MS = 5000; // 刷新冷却时间 const [imageViewer, setImageViewer] = useState<{ open: boolean; src?: string; alt?: string }>({ open: false }); + const THEME_PREF_KEY = 'ThemePref'; + const userPrefRef = useRef(false); const openImageViewer = (src?: string, alt?: string) => { if (!src) return; @@ -67,9 +69,16 @@ function App() { if (containerRef.current) containerRef.current.scrollTop = 0; }; - // 移除触摸下拉刷新逻辑 - - // 撤销 Pointer 事件回退,恢复为纯 Touch 逻辑 + const handleToggleTheme = () => { + setIsDarkMode(prev => { + const next = !prev; + try { + localStorage.setItem(THEME_PREF_KEY, next ? 'dark' : 'light'); + userPrefRef.current = true; + } catch {} + return next; + }); + }; const onWheel: React.WheelEventHandler = (e) => { const atTop = (containerRef.current?.scrollTop ?? 0) <= 0; @@ -78,6 +87,43 @@ function App() { } }; + useEffect(() => { + const mql = typeof window !== 'undefined' && window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null; + let handler: ((e: MediaQueryListEvent) => void) | null = null; + try { + const saved = localStorage.getItem(THEME_PREF_KEY); + if (saved === 'dark' || saved === 'light') { + userPrefRef.current = true; + setIsDarkMode(saved === 'dark'); + } else { + if (mql) { + setIsDarkMode(mql.matches); + handler = (e: MediaQueryListEvent) => { + if (!userPrefRef.current) { + setIsDarkMode(e.matches); + } + }; + if ('addEventListener' in mql) { + mql.addEventListener('change', handler); + } else { + // @ts-ignore + mql.addListener(handler); + } + } + } + } catch {} + return () => { + if (mql && handler) { + if ('removeEventListener' in mql) { + mql.removeEventListener('change', handler); + } else { + // @ts-ignore + mql.removeListener(handler); + } + } + }; + }, []); + useEffect(() => { const controller = new AbortController(); const signal = controller.signal; @@ -115,7 +161,7 @@ function App() { - setIsDarkMode(!isDarkMode)} />}> + }> - {/* 刷新提示改为 toast,不显示顶部灰字 */} {articles.map((article, index) => { if (articles.length === index + 1 && hasMore) { return ( @@ -178,7 +222,7 @@ function App() { } /> } /> - setIsDarkMode(!isDarkMode)} />} /> + } /> } /> diff --git a/src/api.ts b/src/api.ts index 56f0b7f..f87ce2b 100644 --- a/src/api.ts +++ b/src/api.ts @@ -47,7 +47,7 @@ export const voteArticle = async ( throw new Error(`Vote ${type} failed`); } } catch (error) { - toast.error(`点赞${type === 'up' ? '赞' : '踩'}失败`); + toast.error(`点${type === 'up' ? '赞' : '踩'}失败`); throw error; } }; diff --git a/src/components/AboutPage.tsx b/src/components/AboutPage.tsx index 127f718..0c04a3f 100644 --- a/src/components/AboutPage.tsx +++ b/src/components/AboutPage.tsx @@ -67,6 +67,21 @@ const useStyles = makeStyles({ textDecoration: 'underline', backgroundColor: 'transparent', }, + '& a': { + color: tokens.colorBrandForegroundLink, + textDecoration: 'underline', + wordBreak: 'break-word', + }, + '& a:hover': { + textDecoration: 'underline', + }, + '& a:visited': { + color: tokens.colorBrandForegroundLink, + }, + '& a:focus': { + outline: `2px solid ${tokens.colorNeutralStroke1}`, + outlineOffset: '2px', + }, }, }); diff --git a/src/components/PostCard.tsx b/src/components/PostCard.tsx index ec72cbe..6babb6e 100644 --- a/src/components/PostCard.tsx +++ b/src/components/PostCard.tsx @@ -92,13 +92,29 @@ const useStyles = makeStyles({ textDecoration: 'underline', backgroundColor: 'transparent', }, + // 链接样式与交互状态 + '& a': { + color: tokens.colorBrandForegroundLink, + textDecoration: 'underline', + wordBreak: 'break-word', + }, + '& a:hover': { + textDecoration: 'underline', + }, + '& a:visited': { + color: tokens.colorBrandForegroundLink, + }, + '& a:focus': { + outline: `2px solid ${tokens.colorNeutralStroke1}`, + outlineOffset: '2px', + }, // 约束 Markdown 图片不溢出卡片 '& img': { maxWidth: '100%', height: 'auto', display: 'block', borderRadius: tokens.borderRadiusSmall, - }, + } }, actions: { display: 'grid', diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx index e7d3ee9..9b5d2ed 100644 --- a/src/layouts/MainLayout.tsx +++ b/src/layouts/MainLayout.tsx @@ -40,6 +40,7 @@ const useStyles = makeStyles({ content: { flex: '1 1 auto', padding: '20px', + paddingBottom: '64px', overflowY: 'auto', display: 'flex', flexDirection: 'column', diff --git a/src/layouts/components/Footer.tsx b/src/layouts/components/Footer.tsx index ecd8555..977a322 100644 --- a/src/layouts/components/Footer.tsx +++ b/src/layouts/components/Footer.tsx @@ -19,7 +19,10 @@ const useStyles = makeStyles({ textAlign: 'center', // 约束可能的图片或表格 '& img': { maxWidth: '100%', height: 'auto', display: 'inline-block', verticalAlign: 'middle' }, - '& a': { color: tokens.colorBrandForegroundLink }, + '& a': { color: tokens.colorBrandForegroundLink, textDecoration: 'underline', wordBreak: 'break-word' }, + '& a:hover': { textDecoration: 'underline' }, + '& a:visited': { color: tokens.colorBrandForegroundLink }, + '& a:focus': { outline: `2px solid ${tokens.colorNeutralStroke1}`, outlineOffset: '2px' }, }, });