refactor: remove meaningless comments

Remove 50+ noise comments across the codebase:
- AI-generated verbose prose (💡 emoji, '超进化', '大闸', etc.)
- Comments that restate what the code obviously does
- Comments about deleted code
- Import-level noise comments
- Overly verbose Chinese section markers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
雨霖铃
2026-05-28 20:37:52 +08:00
parent b79fe7dd49
commit e140af7698
34 changed files with 32 additions and 242 deletions
-1
View File
@@ -51,7 +51,6 @@ export default function SwitchButtonGroup<T extends string | number = string>({
type="button" type="button"
onClick={() => onChange(option.value)} onClick={() => onChange(option.value)}
className={cn( className={cn(
// 4. 完美继承 shadcn 的 Tabs 交互和动效微调
'flex-1 inline-flex items-center justify-center font-medium whitespace-nowrap transition-all', 'flex-1 inline-flex items-center justify-center font-medium whitespace-nowrap transition-all',
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2', 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50', 'disabled:pointer-events-none disabled:opacity-50',
-2
View File
@@ -125,7 +125,6 @@ function getOrCreatePopover(): HTMLElement {
return popover; return popover;
} }
// 💡 2. 安全防线:字符实体转义沙箱,彻底掐灭任意恶意脚本的执行通道
function escapeHtml(text: string): string { function escapeHtml(text: string): string {
const map: Record<string, string> = { const map: Record<string, string> = {
'&': '&amp;', '&': '&amp;',
@@ -171,7 +170,6 @@ export function showPopover(
): void { ): void {
const popover = getOrCreatePopover(); const popover = getOrCreatePopover();
// 💡 3. 坚固的无障碍绑定:废除违规的行内 inline onclick,改用标准原生节点监听
popover.innerHTML = ''; popover.innerHTML = '';
if (title) { if (title) {
+1 -3
View File
@@ -44,8 +44,6 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
useContextMenuData({ featureKey: 'base64Converter', onData: handleContextMenuData }); useContextMenuData({ featureKey: 'base64Converter', onData: handleContextMenuData });
// 💡 4. 贯彻方案 A(彻底消灭 setOutput / setError):
// 让所有的转化逻辑、类型安全校验在 useMemo 内存管道中单次渲染一气呵成!
const conversionPipeline = useMemo(() => { const conversionPipeline = useMemo(() => {
const trimmed = debouncedInput.trim(); const trimmed = debouncedInput.trim();
if (!trimmed) return { output: '', error: null }; if (!trimmed) return { output: '', error: null };
@@ -112,7 +110,7 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
placeholder={placeholder} placeholder={placeholder}
value={input} value={input}
onChange={setInput} onChange={setInput}
externalError={error || undefined} // 💡 流式异常大闸动态注入 externalError={error || undefined}
showClear={true} showClear={true}
allowCopy={true} allowCopy={true}
minRows={5} minRows={5}
@@ -130,7 +130,6 @@ export function useBase64Converter({ mode }: UseBase64ConverterProps) {
return ''; return '';
}, [customFileName, decoded]); }, [customFileName, decoded]);
// 💡 托管最大文件体积字符串算子,清除下游引入风险
const maxFileSizeStr = `${MAX_FILE_SIZE / 1024 / 1024} MB`; const maxFileSizeStr = `${MAX_FILE_SIZE / 1024 / 1024} MB`;
return { return {
+1 -1
View File
@@ -3,7 +3,7 @@ import { Download, Trash2 } from 'lucide-react';
import { useI18n } from '@/utils/chromeI18n'; import { useI18n } from '@/utils/chromeI18n';
import CopyButton from '@/components/CopyButton'; import CopyButton from '@/components/CopyButton';
import SwitchButtonGroup from '@/components/SwitchButtonGroup'; import SwitchButtonGroup from '@/components/SwitchButtonGroup';
import { Button } from '@/components/ui/button'; // 💡 1. 全面回归规范:引入原生的 shadcn 原子 Button import { Button } from '@/components/ui/button';
import { useStorageState } from '@/utils/useStorageState'; import { useStorageState } from '@/utils/useStorageState';
import type { HtmlToMarkdownPreviewMode } from '@/types/storage'; import type { HtmlToMarkdownPreviewMode } from '@/types/storage';
import { downloadMarkdownFile, htmlToMarkdown, SAMPLE_HTML } from '@/utils/htmlToMarkdown'; import { downloadMarkdownFile, htmlToMarkdown, SAMPLE_HTML } from '@/utils/htmlToMarkdown';
+3 -9
View File
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react'; import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useI18n } from '@/utils/chromeI18n'; import { useI18n } from '@/utils/chromeI18n';
import { cn } from '@/lib/utils'; // 1. 引入标准的 shadcn 工具函数 import { cn } from '@/lib/utils';
export interface DiffNavigatorProps extends React.HTMLAttributes<HTMLDivElement> { export interface DiffNavigatorProps extends React.HTMLAttributes<HTMLDivElement> {
total: number; total: number;
@@ -21,11 +21,9 @@ export default function DiffNavigator({
}: DiffNavigatorProps) { }: DiffNavigatorProps) {
const { t } = useI18n('jsonDiff'); const { t } = useI18n('jsonDiff');
// 计算当前的边界禁用状态守卫
const isFirst = currentIndex <= 0; const isFirst = currentIndex <= 0;
const isLast = currentIndex >= total - 1; const isLast = currentIndex >= total - 1;
// 2. 空状态面板:对齐 shadcn 规范的中性低调卡片
if (total === 0) { if (total === 0) {
return ( return (
<div <div
@@ -45,9 +43,6 @@ export default function DiffNavigator({
return ( return (
<div <div
className={cn( className={cn(
// 3. 完美适配暗黑模式:
// 废除 bg-primary/10,采用标准的低阻尼中性色 bg-secondary/60 配合 border-border/80
// 在任何主题皮肤下都能呈现出高级的暗钛金控制栏质感。
'inline-flex items-center justify-center gap-3 px-3 h-9 rounded-md border border-border/80 bg-secondary/60 shadow-sm', 'inline-flex items-center justify-center gap-3 px-3 h-9 rounded-md border border-border/80 bg-secondary/60 shadow-sm',
className, className,
)} )}
@@ -62,13 +57,12 @@ export default function DiffNavigator({
className={cn( className={cn(
'p-1 rounded-md text-muted-foreground hover:bg-accent hover:text-foreground', 'p-1 rounded-md text-muted-foreground hover:bg-accent hover:text-foreground',
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring', 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'disabled:pointer-events-none disabled:opacity-30', // 4. 边界拦截:触顶时优雅淡化并锁死点击 'disabled:pointer-events-none disabled:opacity-30',
)} )}
> >
<ChevronLeft className="h-4 w-4" /> <ChevronLeft className="h-4 w-4" />
</button> </button>
{/* 计数看板:强制等宽防止数字长短不一时产生宽度挤压跳动 */}
<span className="text-xs font-bold font-mono min-w-[54px] text-center text-foreground/90 tabular-nums select-none"> <span className="text-xs font-bold font-mono min-w-[54px] text-center text-foreground/90 tabular-nums select-none">
{currentIndex + 1} <span className="text-muted-foreground/60 font-sans mx-0.5">/</span>{' '} {currentIndex + 1} <span className="text-muted-foreground/60 font-sans mx-0.5">/</span>{' '}
{total} {total}
@@ -83,7 +77,7 @@ export default function DiffNavigator({
className={cn( className={cn(
'p-1 rounded-md text-muted-foreground hover:bg-accent hover:text-foreground', 'p-1 rounded-md text-muted-foreground hover:bg-accent hover:text-foreground',
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring', 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'disabled:pointer-events-none disabled:opacity-30', // 4. 边界拦截:触底时优雅淡化并锁死点击 'disabled:pointer-events-none disabled:opacity-30',
)} )}
> >
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />
-11
View File
@@ -4,7 +4,6 @@ import { cn } from '@/lib/utils';
import JsonTree from './JsonTree'; import JsonTree from './JsonTree';
import type { DiffNode, DiffResult as DiffResultType, DiffType, ViewMode } from './types'; import type { DiffNode, DiffResult as DiffResultType, DiffType, ViewMode } from './types';
// 💡 顶层 Interface 继承原生 HTML 容器属性,扩展灵活性
export interface DiffResultProps extends React.HTMLAttributes<HTMLDivElement> { export interface DiffResultProps extends React.HTMLAttributes<HTMLDivElement> {
result: DiffResultType; result: DiffResultType;
viewMode: ViewMode; viewMode: ViewMode;
@@ -39,10 +38,6 @@ export default function DiffResult({
} }
return ( return (
/* 1. 单栏拍平视图容器:
- 对齐 shadcn 规范,使用 bg-card、border-border 隔离。
- 注入 tabular-nums 配合 font-mono,消灭任何行高和字符抖动。
*/
<div <div
className={cn( className={cn(
'rounded-xl border border-border bg-card font-mono text-xs shadow-sm overflow-x-auto min-h-[200px] max-h-[520px] overflow-y-auto p-1.5', 'rounded-xl border border-border bg-card font-mono text-xs shadow-sm overflow-x-auto min-h-[200px] max-h-[520px] overflow-y-auto p-1.5',
@@ -79,8 +74,6 @@ const prefixForType = (type: DiffType): string => {
return ' '; return ' ';
}; };
// 2. 状态色彩超进化:
// 拒绝硬编码实色系,全部换用高度安全的语义色变体与暗黑模式自适应。
const typeThemeMap = { const typeThemeMap = {
added: { added: {
text: 'text-emerald-600 dark:text-emerald-400', text: 'text-emerald-600 dark:text-emerald-400',
@@ -191,7 +184,6 @@ interface UnifiedRowProps {
} }
const UnifiedRow = ({ depth, type, text, active, multiline }: UnifiedRowProps) => { const UnifiedRow = ({ depth, type, text, active, multiline }: UnifiedRowProps) => {
// 3. 高精度提取状态样式映射
const currentTheme = typeThemeMap[type] || typeThemeMap.unchanged; const currentTheme = typeThemeMap[type] || typeThemeMap.unchanged;
return ( return (
@@ -200,17 +192,14 @@ const UnifiedRow = ({ depth, type, text, active, multiline }: UnifiedRowProps) =
'flex items-start w-full font-mono py-0.5 select-text group', 'flex items-start w-full font-mono py-0.5 select-text group',
currentTheme.bg, currentTheme.bg,
currentTheme.text, currentTheme.text,
// 4. 高亮定位条:不再使用生硬的蓝圆环,改为现代编辑器的“侧边左高亮带”设计,质感直接拉满
active && active &&
'bg-primary/10 relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-1 before:bg-blue-500', 'bg-primary/10 relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-1 before:bg-blue-500',
)} )}
style={{ style={{
// 维持高精度的 Padding 基线缩进
paddingLeft: `${Math.max(0.5, depth * 1.25)}rem`, paddingLeft: `${Math.max(0.5, depth * 1.25)}rem`,
paddingRight: '0.5rem', paddingRight: '0.5rem',
}} }}
> >
{/* 5. 前缀标识:等宽锁定,强行占据 w-5 并让符号居中对齐,达成 VSCode 般的整洁排版 */}
<span className="font-bold w-5 shrink-0 text-center select-none opacity-70 tabular-nums"> <span className="font-bold w-5 shrink-0 text-center select-none opacity-70 tabular-nums">
{prefixForType(type)} {prefixForType(type)}
</span> </span>
+3 -8
View File
@@ -32,7 +32,7 @@ export default function JsonConvertSection({
const pk = translationPrefix; const pk = translationPrefix;
// 1. 高阶性能调优:将文本变化收拢进行 250ms 极速防抖落盘,避免每一次敲击键盘都触发底层的复杂序列化算法 // Debounce input
useEffect(() => { useEffect(() => {
const handle = setTimeout(() => { const handle = setTimeout(() => {
setDebouncedInput(input); setDebouncedInput(input);
@@ -40,14 +40,10 @@ export default function JsonConvertSection({
return () => clearTimeout(handle); return () => clearTimeout(handle);
}, [input]); }, [input]);
// 💡 2. 贯彻方案 A(衍生变量超进化):
// 彻底删掉 error 状态和对应的受控 useEffect 节点。
// 语法错误由防抖文本在内存中同步推导,彻底斩断二次级联渲染链条,ESLint 警告自愈!
const error = useMemo(() => { const error = useMemo(() => {
return validateJson(debouncedInput); return validateJson(debouncedInput);
}, [debouncedInput]); }, [debouncedInput]);
// 3. 核心魔法:纯净的即时流式转换转换管线 (Live Compilation Pipeline)
const conversionPipeline = useMemo(() => { const conversionPipeline = useMemo(() => {
const trimmed = debouncedInput.trim(); const trimmed = debouncedInput.trim();
if (!trimmed || error) return null; if (!trimmed || error) return null;
@@ -63,7 +59,6 @@ export default function JsonConvertSection({
} }
}, [debouncedInput, error, convertFunction]); }, [debouncedInput, error, convertFunction]);
// 判定运行时异常
const runtimeError = const runtimeError =
conversionPipeline && 'isRuntimeError' in conversionPipeline conversionPipeline && 'isRuntimeError' in conversionPipeline
? conversionPipeline.errorMessage ? conversionPipeline.errorMessage
@@ -88,7 +83,7 @@ export default function JsonConvertSection({
onClear={() => setInput('')} onClear={() => setInput('')}
/> />
{/* 4. 结果展示或状态引导卡片区 */} {/* Result display */}
{result && result.output ? ( {result && result.output ? (
<div className="relative rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden"> <div className="relative rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden">
{/* 结果栏精致头部 */} {/* 结果栏精致头部 */}
@@ -130,7 +125,7 @@ export default function JsonConvertSection({
</div> </div>
</div> </div>
) : ( ) : (
/* 5. 空状态提示容器:完美的中性虚线引导,不喧宾夺主 */ /* Empty state */
<div className="p-8 rounded-xl bg-muted/30 border border-dashed border-border/80 text-center flex flex-col items-center justify-center min-h-[120px] select-none"> <div className="p-8 rounded-xl bg-muted/30 border border-dashed border-border/80 text-center flex flex-col items-center justify-center min-h-[120px] select-none">
<p className="text-xs font-semibold text-muted-foreground/80 tracking-wide max-w-[240px] leading-relaxed"> <p className="text-xs font-semibold text-muted-foreground/80 tracking-wide max-w-[240px] leading-relaxed">
{error ? t('jsonFormat:fixErrorHint') : t(`jsonFormat:${pk}EmptyHint`)} {error ? t('jsonFormat:fixErrorHint') : t(`jsonFormat:${pk}EmptyHint`)}
-2
View File
@@ -2,8 +2,6 @@ import React from 'react';
import TextInputArea from '@/components/TextInputArea'; import TextInputArea from '@/components/TextInputArea';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
// 💡 核心修复:使用 Omit<..., 'onChange'> 强行挖掉原生的 onChange 签名
// 这样我们自定义的 (value: string) => void 就能独占鳌头,彻底消灭 TS2430 接口冲突!
export interface JsonDiffInputProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> { export interface JsonDiffInputProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
label: string; label: string;
placeholder: string; placeholder: string;
+2 -8
View File
@@ -21,7 +21,7 @@ export default function JsonFormatSection() {
const [indentSize, setIndentSize] = useState<number>(2); const [indentSize, setIndentSize] = useState<number>(2);
const [sortKeys, setSortKeys] = useState(false); const [sortKeys, setSortKeys] = useState(false);
// 1. 高频打字防抖落盘:防止大体积 JSON 在高频输入时发生卡顿 // Debounce input
useEffect(() => { useEffect(() => {
const handle = setTimeout(() => { const handle = setTimeout(() => {
setDebouncedInput(input); setDebouncedInput(input);
@@ -29,14 +29,11 @@ export default function JsonFormatSection() {
return () => clearTimeout(handle); return () => clearTimeout(handle);
}, [input]); }, [input]);
// 💡 2. 贯彻方案 A(衍生变量超进化):
// 彻底删除原有的 setError 状态和相关的 useEffect。
// 语法错误由防抖文本在内存中同步推导,彻底斩断二次级联渲染链条,ESLint 警告瞬间消亡!
const error = useMemo(() => { const error = useMemo(() => {
return validateJson(debouncedInput); return validateJson(debouncedInput);
}, [debouncedInput]); }, [debouncedInput]);
// 3. 实时流式格式化管线 // Real-time formatting pipeline
const formattedPipeline = useMemo(() => { const formattedPipeline = useMemo(() => {
const trimmed = debouncedInput.trim(); const trimmed = debouncedInput.trim();
if (!trimmed || error) return null; if (!trimmed || error) return null;
@@ -149,9 +146,6 @@ export default function JsonFormatSection() {
/> />
</div> </div>
{/* 核心格式化数据面板:
💡 修复点:移除了互相打架的 select-all 类名,仅保留纯正的代码高亮可选样式 select-text
*/}
<div className="p-4 font-mono text-xs text-foreground/90 whitespace-pre-wrap break-all max-h-[420px] overflow-y-auto leading-relaxed select-text"> <div className="p-4 font-mono text-xs text-foreground/90 whitespace-pre-wrap break-all max-h-[420px] overflow-y-auto leading-relaxed select-text">
{result.formatted} {result.formatted}
</div> </div>
-7
View File
@@ -38,7 +38,6 @@ const getValueForSide = (node: DiffNode, side: TreeSide): unknown => {
return side === 'left' ? node.oldValue : node.newValue; return side === 'left' ? node.oldValue : node.newValue;
}; };
// 1. 核心状态色彩映射调色盘:完美自适应双色模式
const typeThemeMap = { const typeThemeMap = {
added: { added: {
text: 'text-emerald-600 dark:text-emerald-400', text: 'text-emerald-600 dark:text-emerald-400',
@@ -62,10 +61,6 @@ const isContainerValue = (v: unknown): boolean => {
return (typeof v === 'object' && v !== null) || Array.isArray(v); return (typeof v === 'object' && v !== null) || Array.isArray(v);
}; };
/**
* 💡 性能调优大闸:将 NodeRow 抽离为顶层独立组件并裹上 React.memo。
* 配合精准的 Props Diff,使得某一行的展开闭合绝对不会连累到其他平级和上级节点。
*/
const NodeRow = React.memo( const NodeRow = React.memo(
({ node, side, depth, defaultExpandDepth, activePath, isLastChild }: NodeRowProps) => { ({ node, side, depth, defaultExpandDepth, activePath, isLastChild }: NodeRowProps) => {
const [override, setOverride] = useState<'auto' | 'open' | 'closed'>('auto'); const [override, setOverride] = useState<'auto' | 'open' | 'closed'>('auto');
@@ -113,8 +108,6 @@ const NodeRow = React.memo(
const isActive = activePath === node.path; const isActive = activePath === node.path;
const isRoot = depth === 0; const isRoot = depth === 0;
// 缩进样式封装:
// 💡 视觉魔法:通过在左侧追加 before 细线,在每一层级下自动垂下一条优雅的 IDE 级“缩进指引线”
const indentStyle = { const indentStyle = {
paddingLeft: `${Math.max(0.25, depth * 1.15)}rem`, paddingLeft: `${Math.max(0.25, depth * 1.15)}rem`,
}; };
+2 -4
View File
@@ -43,7 +43,7 @@ const diffNode = (
return { return {
key, key,
type: 'added', type: 'added',
oldValue: undefined, // 💡 补齐:对齐移除 ? 后的类型规范 oldValue: undefined,
newValue: right, newValue: right,
path, path,
isLeaf: !isObject(right) && !isArray(right), isLeaf: !isObject(right) && !isArray(right),
@@ -58,7 +58,7 @@ const diffNode = (
key, key,
type: 'removed', type: 'removed',
oldValue: left, oldValue: left,
newValue: undefined, // 💡 补齐:对齐移除 ? 后的类型规范 newValue: undefined,
path, path,
isLeaf: !isObject(left) && !isArray(left), isLeaf: !isObject(left) && !isArray(left),
hasDiffInChildren: false, // 自身即是删除,子树无需向下检索 hasDiffInChildren: false, // 自身即是删除,子树无需向下检索
@@ -87,7 +87,6 @@ const diffNode = (
children.push(diffNode(l, r, k, childPath, diffPaths)); children.push(diffNode(l, r, k, childPath, diffPaths));
}); });
// 💡 核心改良:判定子节点中是否存在任何变动
const hasDiffInChildren = children.some((c) => c.type !== 'unchanged' || c.hasDiffInChildren); const hasDiffInChildren = children.some((c) => c.type !== 'unchanged' || c.hasDiffInChildren);
return { return {
@@ -115,7 +114,6 @@ const diffNode = (
children[i] = diffNode(l, r, k, childPath, diffPaths); children[i] = diffNode(l, r, k, childPath, diffPaths);
} }
// 💡 核心改良:判定子项中是否存在任何变动
const hasDiffInChildren = children.some((c) => c.type !== 'unchanged' || c.hasDiffInChildren); const hasDiffInChildren = children.some((c) => c.type !== 'unchanged' || c.hasDiffInChildren);
return { return {
+4 -7
View File
@@ -40,11 +40,11 @@ export default function Index() {
const { t } = useI18n(['jsonDiff', 'jsonFormat']); const { t } = useI18n(['jsonDiff', 'jsonFormat']);
const [pageMode, setPageMode] = useStorageState('jsonTools/pageMode', 'diff', isValidPageMode); const [pageMode, setPageMode] = useStorageState('jsonTools/pageMode', 'diff', isValidPageMode);
// 1. 受控原始输入源 // Debounce input
const [leftInput, setLeftInput] = useState(''); const [leftInput, setLeftInput] = useState('');
const [rightInput, setRightInput] = useState(''); const [rightInput, setRightInput] = useState('');
// 2. 纯净的异步防抖管道:仅负责切断高频打字开销 // Debounced values
const [debouncedLeft, setDebouncedLeft] = useState(''); const [debouncedLeft, setDebouncedLeft] = useState('');
const [debouncedRight, setDebouncedRight] = useState(''); const [debouncedRight, setDebouncedRight] = useState('');
@@ -56,7 +56,7 @@ export default function Index() {
return () => clearTimeout(handle); return () => clearTimeout(handle);
}, [leftInput, rightInput]); }, [leftInput, rightInput]);
// 3. 贯彻方案A:利用 useMemo 将防抖文本同步转化为解析树和错误提示 // Parse debounced inputs
const parseState = useMemo(() => { const parseState = useMemo(() => {
const invalidMsg = t('jsonDiff:invalidJson'); const invalidMsg = t('jsonDiff:invalidJson');
return { return {
@@ -71,7 +71,7 @@ export default function Index() {
const [viewMode, setViewMode] = useState<ViewMode>('sideBySide'); const [viewMode, setViewMode] = useState<ViewMode>('sideBySide');
const [currentDiffIndex, setCurrentDiffIndex] = useState(0); const [currentDiffIndex, setCurrentDiffIndex] = useState(0);
// 4. 实时比对流式计算 // Real-time diff computation
const diffResult = useMemo(() => { const diffResult = useMemo(() => {
const { left, right } = parseState; const { left, right } = parseState;
if (left.error || right.error || debouncedLeft.trim() === '' || debouncedRight.trim() === '') { if (left.error || right.error || debouncedLeft.trim() === '' || debouncedRight.trim() === '') {
@@ -80,9 +80,6 @@ export default function Index() {
return diffJson(left.value, right.value); return diffJson(left.value, right.value);
}, [parseState, debouncedLeft, debouncedRight]); }, [parseState, debouncedLeft, debouncedRight]);
// 💡 彻底删除了原本在此处的侦听 [diffResult] 的 useEffect。
// 状态重置已完全委托给事件源头,级联更新警告从根源上永久自愈!
const total = diffResult?.diffPaths.length ?? 0; const total = diffResult?.diffPaths.length ?? 0;
const handlePrev = useCallback(() => { const handlePrev = useCallback(() => {
+1 -6
View File
@@ -9,7 +9,7 @@ import { cn } from '@/lib/utils';
interface SectionProps { interface SectionProps {
title: string; title: string;
content: unknown; content: unknown;
colorClass: string; // 💡 1. 废除硬编码十六进制色值,改用语义化的 Tailwind 类名 colorClass: string;
bgClass: string; bgClass: string;
borderClass: string; borderClass: string;
} }
@@ -27,10 +27,6 @@ const Section = ({ title, content, colorClass, bgClass, borderClass }: SectionPr
className="h-6 w-6 rounded-md border text-muted-foreground" className="h-6 w-6 rounded-md border text-muted-foreground"
/> />
</div> </div>
{/* 💡 排版微距精雕:
- 彻底移除 border-black/5 这种非暗黑模式友好的硬隔离。
- 统一收拢为标准的 bg-muted/40 配合 font-mono text-xs
*/}
<pre className="m-0 p-3 bg-muted/30 dark:bg-muted/10 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-border/50 text-foreground/90 leading-relaxed select-text"> <pre className="m-0 p-3 bg-muted/30 dark:bg-muted/10 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-border/50 text-foreground/90 leading-relaxed select-text">
{content ? stringifyJson(content) : t('jwt:invalidFormat')} {content ? stringifyJson(content) : t('jwt:invalidFormat')}
</pre> </pre>
@@ -59,7 +55,6 @@ export default function Index() {
useContextMenuData({ featureKey: 'jwt', onData: handleContextMenuData }); useContextMenuData({ featureKey: 'jwt', onData: handleContextMenuData });
// 3. 贯彻方案 A:衍生变量流。直接消费防抖后的文本
const result = useMemo(() => { const result = useMemo(() => {
if (!debouncedInput.trim()) { if (!debouncedInput.trim()) {
return null; return null;
-3
View File
@@ -18,7 +18,6 @@ import { cn } from '@/lib/utils';
const isValidPreviewMode = (val: unknown): val is MarkdownToHtmlPreviewMode => const isValidPreviewMode = (val: unknown): val is MarkdownToHtmlPreviewMode =>
typeof val === 'string' && ['split', 'preview', 'html'].includes(val); typeof val === 'string' && ['split', 'preview', 'html'].includes(val);
// 💡 规范回归:保持最纯净的通用选择器集合,内部变量全部交由全局 :root 驱动
const PREVIEW_STYLES = ` const PREVIEW_STYLES = `
.markdown-body { .markdown-body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -94,7 +93,6 @@ export default function MarkdownToHtmlPage() {
const result = useMemo(() => markdownToHtml(markdown), [markdown]); const result = useMemo(() => markdownToHtml(markdown), [markdown]);
const error = result.hasError ? (result.error ?? null) : null; const error = result.hasError ? (result.error ?? null) : null;
// 💡 自适应大总管:以极高严谨度拼装明暗大闸,用标准换行切断粘连风险
useEffect(() => { useEffect(() => {
const iframe = iframeRef.current; const iframe = iframeRef.current;
if (!iframe) return; if (!iframe) return;
@@ -123,7 +121,6 @@ export default function MarkdownToHtmlPage() {
--md-link-color: #3b82f6; --md-link-color: #3b82f6;
}`; }`;
// 💡 3. 核心大清洗:将全局基础树(html, body)与派生样式完全独立硬编码,杜绝任何语法踩踏
const baseGlobalStyles = ` const baseGlobalStyles = `
html, body { html, body {
margin: 0; margin: 0;
@@ -11,17 +11,12 @@ export default function GeneratePanel() {
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5">
{/* 左翼:高性能受控输入翼终端 */}
<div <div
className={cn( className={cn(
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4', 'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4',
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring', 'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
)} )}
> >
{/* 💡 2. 独立外置标签架(A11y 无障碍对齐):
- 彻底删掉 TextInputArea 上引发崩溃的违规属性。
- 改用正统的 <Label />,并注入标准的高度无障碍样式,间距比例极度平滑。
*/}
<div className="flex flex-col space-y-2.5 h-full"> <div className="flex flex-col space-y-2.5 h-full">
<Label className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider select-none pl-0.5"> <Label className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider select-none pl-0.5">
{t('qrCode:urlInputLabel')} {t('qrCode:urlInputLabel')}
@@ -44,7 +39,6 @@ export default function GeneratePanel() {
</div> </div>
</div> </div>
{/* 右翼:活态二维码高精生成区 */}
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<QrCodePreview <QrCodePreview
qrCodeDataUrl={generatorState.qrCodeDataUrl} qrCodeDataUrl={generatorState.qrCodeDataUrl}
@@ -18,7 +18,6 @@ export default function ParsePanel() {
const items = e.clipboardData?.items; const items = e.clipboardData?.items;
if (!items) return; if (!items) return;
// 检查是否有图片
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) { if (items[i].type.startsWith('image/')) {
e.preventDefault(); e.preventDefault();
@@ -31,7 +30,6 @@ export default function ParsePanel() {
} }
} }
// 检查是否有 Base64 字符串
const text = e.clipboardData?.getData('text/plain'); const text = e.clipboardData?.getData('text/plain');
if (text && text.startsWith('data:image/')) { if (text && text.startsWith('data:image/')) {
e.preventDefault(); e.preventDefault();
@@ -58,11 +56,7 @@ export default function ParsePanel() {
}, [handlePaste]); }, [handlePaste]);
return ( return (
/* 💡 统一大视觉轴:
- 追加 p-0.5 微隔离,配合 gap-6 建立与生成面板(GeneratePanel)绝对像素对齐的网格天平。
*/
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5">
{/* 左翼:图片接收/拖拽/剪贴板上传终端 */}
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<ImageUploader <ImageUploader
selectedFile={parserState.selectedFile} selectedFile={parserState.selectedFile}
@@ -75,16 +69,13 @@ export default function ParsePanel() {
/> />
</div> </div>
{/* 右翼:高阶解析出码只读终端 */}
<div <div
className={cn( className={cn(
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4', 'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4',
// 💡 视觉对称增强:加入相同的聚焦变量环联动,使双翼权重达成完美绝对平衡
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring', 'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
)} )}
> >
<div className="flex flex-col space-y-2.5 h-full"> <div className="flex flex-col space-y-2.5 h-full">
{/* 💡 修复点:物理剔除 TextInputArea 上的违规 title,改用符合 Vercel 美学的极致大写极细原子标签 */}
<Label className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider pl-0.5"> <Label className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider pl-0.5">
{t('qrCode:resultLabel')} {t('qrCode:resultLabel')}
</Label> </Label>
+3 -8
View File
@@ -1,23 +1,19 @@
import type { Dispatch, SetStateAction } from 'react'; // 💡 1. 显式解构导入类型,彻底掐灭 TS2304 报错 import type { Dispatch, SetStateAction } from 'react';
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import type { QrCodeGeneratorState, QrCodeMode, QrCodeParserState } from '../types'; import type { QrCodeGeneratorState, QrCodeMode, QrCodeParserState } from '../types';
export interface QrCodeContextValue { export interface QrCodeContextValue {
// 核心主视图路由模式切换卡
mode: QrCodeMode; mode: QrCodeMode;
setMode: (mode: QrCodeMode) => void; setMode: (mode: QrCodeMode) => void;
// 1. 流式生成器终端状态机驱动
generatorState: QrCodeGeneratorState; generatorState: QrCodeGeneratorState;
setTextToEncode: (text: string) => void; setTextToEncode: (text: string) => void;
// 💡 架构纯净化:物理剔除暴露给外部的命令式 generateQrCode 算子。
// 外部面板只需 setTextToEncode 驱动源文本更新,生成动作由内部流式管线全自动自发自愈完成!
downloadQrCode: () => void; downloadQrCode: () => void;
copyQrCode: () => Promise<void>; copyQrCode: () => Promise<void>;
// 2. 活态反向解析器终端状态机驱动
parserState: QrCodeParserState; parserState: QrCodeParserState;
setParserState: Dispatch<SetStateAction<QrCodeParserState>>; // 💡 规整为纯净的直接类型使用 setParserState: Dispatch<SetStateAction<QrCodeParserState>>;
parseQrCode: (file: File) => Promise<void>; parseQrCode: (file: File) => Promise<void>;
handleFileChange: (file: File) => void; handleFileChange: (file: File) => void;
handleClearFile: () => void; handleClearFile: () => void;
@@ -28,7 +24,6 @@ export const QrCodeContext = createContext<QrCodeContextValue | null>(null);
export function useQrCodeContext() { export function useQrCodeContext() {
const context = useContext(QrCodeContext); const context = useContext(QrCodeContext);
if (!context) { if (!context) {
// 边界鲁棒性防护大闸
throw new Error('useQrCodeContext must be used within a valid QrCodeProvider container'); throw new Error('useQrCodeContext must be used within a valid QrCodeProvider container');
} }
return context; return context;
-15
View File
@@ -12,10 +12,8 @@ export function useQrCode(): QrCodeContextValue {
const { t } = useI18n('qrCode'); const { t } = useI18n('qrCode');
const { showMessage } = useSnackbar(); const { showMessage } = useSnackbar();
// 核心路由视图模式
const [mode, setMode] = useState<QrCodeMode>('generate'); const [mode, setMode] = useState<QrCodeMode>('generate');
// 1. 生成器状态流(大幅瘦身:剔除 generating 状态)
const [generatorState, setGeneratorState] = useState< const [generatorState, setGeneratorState] = useState<
Omit<QrCodeGeneratorState, 'generating' | 'qrCodeDataUrl'> Omit<QrCodeGeneratorState, 'generating' | 'qrCodeDataUrl'>
>({ >({
@@ -23,7 +21,6 @@ export function useQrCode(): QrCodeContextValue {
inputError: '', inputError: '',
}); });
// 2. 解析器状态流
const [parserState, setParserState] = useState<QrCodeParserState>({ const [parserState, setParserState] = useState<QrCodeParserState>({
decodedResult: '', decodedResult: '',
parsing: false, parsing: false,
@@ -33,12 +30,8 @@ export function useQrCode(): QrCodeContextValue {
dragging: false, dragging: false,
}); });
// 3. 高频打字极速防抖
const debouncedTextToEncode = useDebounce(generatorState.textToEncode, 200); const debouncedTextToEncode = useDebounce(generatorState.textToEncode, 200);
// 💡 4. 贯彻方案 A(无副作用超导管线):
// 彻底删除原有的 generateQrCodeRef、3个 useEffect、1个 useRef 以及相关的复杂状态机。
// 二维码画布纯粹作为防抖文本的派生变量同步算出,0重绘死循环风险,体验平滑如镜!
const qrCodeDataUrl = useMemo(() => { const qrCodeDataUrl = useMemo(() => {
const text = debouncedTextToEncode.trim(); const text = debouncedTextToEncode.trim();
if (!text) return ''; if (!text) return '';
@@ -49,14 +42,12 @@ export function useQrCode(): QrCodeContextValue {
url = 'https://' + url; url = 'https://' + url;
} }
// 💡 暗黑模式自适应大闸:实时嗅探系统 DOM 阶度
const isDark = document.documentElement.classList.contains('dark'); const isDark = document.documentElement.classList.contains('dark');
const qr = new QRious({ const qr = new QRious({
value: url, value: url,
size: 260, size: 260,
level: 'H', level: 'H',
// 暗黑模式下使用透明底、月白前景色;白天模式下使用标准现代黑白配
foreground: isDark ? '#f3f4f6' : '#0f172a', foreground: isDark ? '#f3f4f6' : '#0f172a',
background: isDark ? 'transparent' : '#ffffff', background: isDark ? 'transparent' : '#ffffff',
}); });
@@ -68,7 +59,6 @@ export function useQrCode(): QrCodeContextValue {
} }
}, [debouncedTextToEncode]); }, [debouncedTextToEncode]);
// 融合派生数据至完整状态体,满足外部组件强类型契合
const fullGeneratorState = useMemo<QrCodeGeneratorState>( const fullGeneratorState = useMemo<QrCodeGeneratorState>(
() => ({ () => ({
...generatorState, ...generatorState,
@@ -78,12 +68,10 @@ export function useQrCode(): QrCodeContextValue {
[generatorState, qrCodeDataUrl], [generatorState, qrCodeDataUrl],
); );
// 设置输入文本
const setTextToEncode = useCallback((text: string) => { const setTextToEncode = useCallback((text: string) => {
setGeneratorState((prev) => ({ ...prev, textToEncode: text, inputError: '' })); setGeneratorState((prev) => ({ ...prev, textToEncode: text, inputError: '' }));
}, []); }, []);
// 处理右键菜单数据上下文
const handleContextMenuData = useCallback((payload: string) => { const handleContextMenuData = useCallback((payload: string) => {
setMode('generate'); setMode('generate');
setGeneratorState((prev) => ({ ...prev, textToEncode: payload, inputError: '' })); setGeneratorState((prev) => ({ ...prev, textToEncode: payload, inputError: '' }));
@@ -119,7 +107,6 @@ export function useQrCode(): QrCodeContextValue {
[t, showMessage], [t, showMessage],
); );
// 下载二维码
const downloadQrCode = useCallback(() => { const downloadQrCode = useCallback(() => {
if (!qrCodeDataUrl) return; if (!qrCodeDataUrl) return;
@@ -130,7 +117,6 @@ export function useQrCode(): QrCodeContextValue {
showMessage(t('qrCode:qrCodeDownloadSuccess'), { severity: 'success', autoHideDuration: 1000 }); showMessage(t('qrCode:qrCodeDownloadSuccess'), { severity: 'success', autoHideDuration: 1000 });
}, [qrCodeDataUrl, showMessage, t]); }, [qrCodeDataUrl, showMessage, t]);
// 复制二维码至剪贴板
const copyQrCode = useCallback(async () => { const copyQrCode = useCallback(async () => {
if (!qrCodeDataUrl) return; if (!qrCodeDataUrl) return;
@@ -151,7 +137,6 @@ export function useQrCode(): QrCodeContextValue {
} }
}, [qrCodeDataUrl, showMessage, t]); }, [qrCodeDataUrl, showMessage, t]);
// 处理文件选择
const handleFileChange = useCallback( const handleFileChange = useCallback(
(file: File) => { (file: File) => {
setParserState((prev) => { setParserState((prev) => {
-9
View File
@@ -19,12 +19,7 @@ export default function Index() {
return ( return (
<QrCodeContext.Provider value={qrCode}> <QrCodeContext.Provider value={qrCode}>
{/* 💡 统一视觉规范大超进化:
- 彻底剥离破坏流式宽度的 max-w-[400px] 枷锁,开启标准的 w-full 全自适应包裹。
- 替换为标准的 p-4 呼吸内边距配合 flex flex-col space-y-4,接管系统级重排!
*/}
<div className="p-4 w-full flex flex-col space-y-4 min-h-[500px] select-none"> <div className="p-4 w-full flex flex-col space-y-4 min-h-[500px] select-none">
{/* 流式中央控制切流卡:注入 sm 断点防御,防范单栏状态下发生变形 */}
<div className="w-full sm:w-fit pt-0.5"> <div className="w-full sm:w-fit pt-0.5">
<SwitchButtonGroup <SwitchButtonGroup
value={qrCode.mode} value={qrCode.mode}
@@ -34,10 +29,6 @@ export default function Index() {
/> />
</div> </div>
{/* 💡 面板渲染沙箱:
- 在切流渲染时,利用独立的 mt-2 增加纵深边界线。
- 配合内部自带的双翼 Flex 聚焦大边框,形成坚固如铁的架构闭环!
*/}
<div className="w-full pt-1.5"> <div className="w-full pt-1.5">
{qrCode.mode === 'generate' ? ( {qrCode.mode === 'generate' ? (
<div> <div>
+1 -8
View File
@@ -3,7 +3,7 @@ import { CheckCircle, XCircle } from 'lucide-react';
import type { CleaningResult as CleaningResultType } from '@/types/storage'; import type { CleaningResult as CleaningResultType } from '@/types/storage';
import { formatCleaningResult } from '@/utils/storageCleaner'; import { formatCleaningResult } from '@/utils/storageCleaner';
import { useI18n } from '@/utils/chromeI18n'; import { useI18n } from '@/utils/chromeI18n';
import { cn } from '@/lib/utils'; // 1. 引入 shadcn 核心类名合并工具 import { cn } from '@/lib/utils';
interface CleaningResultProps extends React.HTMLAttributes<HTMLDivElement> { interface CleaningResultProps extends React.HTMLAttributes<HTMLDivElement> {
result: CleaningResultType | null; result: CleaningResultType | null;
@@ -18,11 +18,6 @@ export default function CleaningResult({ result, className, ...props }: Cleaning
return ( return (
<div className={cn('w-full', className)} {...props}> <div className={cn('w-full', className)} {...props}>
{/* 2. 彻底重构容器类名结构:
- 成功状态:采用 Tailwind 官方推荐的 emerald 体系,利用 /10 (10% 透明度) 和 /20 (边框)。
- 失败状态:完全放权给标准的 border-destructive/20 和 bg-destructive/5。
- 这样在明暗双色模式切换时,色彩会自动与背景完美融为一体。
*/}
<div <div
className={cn( className={cn(
'flex items-start gap-3 rounded-xl py-2.5 px-3.5 border shadow-sm', 'flex items-start gap-3 rounded-xl py-2.5 px-3.5 border shadow-sm',
@@ -31,14 +26,12 @@ export default function CleaningResult({ result, className, ...props }: Cleaning
: 'bg-destructive/5 border-destructive/20 text-destructive', : 'bg-destructive/5 border-destructive/20 text-destructive',
)} )}
> >
{/* 3. 图标样式向系统语义全面对齐 */}
{isSuccess ? ( {isSuccess ? (
<CheckCircle className="h-4 w-4 shrink-0 mt-0.5 text-emerald-500" /> <CheckCircle className="h-4 w-4 shrink-0 mt-0.5 text-emerald-500" />
) : ( ) : (
<XCircle className="h-4 w-4 shrink-0 mt-0.5 text-destructive" /> <XCircle className="h-4 w-4 shrink-0 mt-0.5 text-destructive" />
)} )}
{/* 4. 文本排版细节微调 */}
<span className="text-xs sm:text-sm font-semibold leading-relaxed break-all"> <span className="text-xs sm:text-sm font-semibold leading-relaxed break-all">
{isSuccess {isSuccess
? formatCleaningResult(result, t) ? formatCleaningResult(result, t)
@@ -10,7 +10,6 @@ export default function ErrorDisplay({ error, className, ...props }: ErrorDispla
const { t } = useI18n('storageCleaner'); const { t } = useI18n('storageCleaner');
return ( return (
// 1. 精简层级:单层外壳直接搞定居中、响应式高度与外部类名扩展
<div <div
className={cn( className={cn(
'flex flex-col items-center justify-center py-8 min-h-[240px] sm:min-h-[360px] p-4 text-center', 'flex flex-col items-center justify-center py-8 min-h-[240px] sm:min-h-[360px] p-4 text-center',
@@ -18,13 +17,7 @@ export default function ErrorDisplay({ error, className, ...props }: ErrorDispla
)} )}
{...props} {...props}
> >
{/* 2. 核心卡片容器:
- 彻底放弃 bg-red-50,改用标准的 bg-destructive/53%~5% 透明度的系统危险色)。
- 边框改为 border-destructive/20。
- 这样在黑夜模式下会自动完美混色,绝不刺眼。
*/}
<div className="w-full max-w-xs flex flex-col items-center justify-center rounded-xl p-5 border border-destructive/20 bg-destructive/5 shadow-sm"> <div className="w-full max-w-xs flex flex-col items-center justify-center rounded-xl p-5 border border-destructive/20 bg-destructive/5 shadow-sm">
{/* 3. 图标与主要错误信息全面对接 text-destructive 语义色 */}
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 text-destructive mb-3.5 shrink-0"> <div className="flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 text-destructive mb-3.5 shrink-0">
<AlertCircle className="h-5 w-5" /> <AlertCircle className="h-5 w-5" />
</div> </div>
@@ -33,7 +26,6 @@ export default function ErrorDisplay({ error, className, ...props }: ErrorDispla
{error} {error}
</p> </p>
{/* 次要提示文本维持柔和的中性高级灰 */}
<p className="text-xs font-medium leading-relaxed text-muted-foreground/90 px-2"> <p className="text-xs font-medium leading-relaxed text-muted-foreground/90 px-2">
{t('storageCleaner:errorStandardOnly')} {t('storageCleaner:errorStandardOnly')}
</p> </p>
-11
View File
@@ -26,14 +26,9 @@ export default function OptionItem({
return ( return (
<div <div
// 3. 跨越级交互升级:将外部容器升级为一个高度敏感的可点击 Tab 热区
onClick={onChange} onClick={onChange}
className={cn( className={cn(
'flex justify-between items-center py-2.5 px-3.5 rounded-xl border cursor-pointer select-none', 'flex justify-between items-center py-2.5 px-3.5 rounded-xl border cursor-pointer select-none',
// 4. 彻底抛弃硬编码黄底:
// - 选中时:使用 bg-primary/5 (系统主色超淡叠加) 配合标准 border-primary/30。
// - 未选中时:保持透明 border-transparent,悬停呈现 bg-muted。
// 这样在暗黑模式下会自动无缝混色,极为深邃、高级。
checked checked
? 'bg-primary/5 border-primary/30 shadow-sm' ? 'bg-primary/5 border-primary/30 shadow-sm'
: 'bg-transparent border-transparent hover:bg-muted/70', : 'bg-transparent border-transparent hover:bg-muted/70',
@@ -64,14 +59,8 @@ export default function OptionItem({
)} )}
</div> </div>
{/* 5. 超进化:全面替换原生 input 标签
完美调用 shadcn 的 Checkbox 组件。它自带全站统一的主色(Primary)、
打钩选中时的平滑微放大缩放动效(Scale Animation),
并且阻止冒泡,防范与外层的全局覆盖点击事件产生双重冲突。
*/}
<Checkbox <Checkbox
checked={checked} checked={checked}
// 阻止 Checkbox 自身的点击事件冒泡,因为外层 div 已经代理了点击逻辑
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onCheckedChange={onChange} onCheckedChange={onChange}
className="h-4 w-4 shrink-0 rounded border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground" className="h-4 w-4 shrink-0 rounded border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
@@ -34,11 +34,6 @@ export function StorageCleanerConfirm({
return ( return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}> <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
{/* 💡 终极修复秘诀:
- 移除原来的 sm:max-w-[360px],改用 max-w-[calc(100%-32px)] 或者是 w-[88%]。
- 这样无论插件弹窗多窄,它的左右两侧都必然会被强制挤出至少 16px 的完美空白护边!
- 将 p-5 转换为明确的 p-6,增大弹窗内部的呼吸感。
*/}
<DialogContent <DialogContent
className={cn( className={cn(
'w-[90%] max-w-[340px] p-6 gap-0 rounded-2xl overflow-hidden shadow-xl border border-border bg-card text-card-foreground', 'w-[90%] max-w-[340px] p-6 gap-0 rounded-2xl overflow-hidden shadow-xl border border-border bg-card text-card-foreground',
@@ -79,11 +74,6 @@ export function StorageCleanerConfirm({
</div> </div>
</div> </div>
{/* 底部操作按钮区:
💡 修复要点:
- 增加 pt-2 隔开上方危险条。
- 显式通过 w-full 配合 flex-col 铺满,在移动端/窄插件下垂直堆叠,最符合小屏直觉。
*/}
<DialogFooter className="flex flex-col gap-2 w-full pt-2"> <DialogFooter className="flex flex-col gap-2 w-full pt-2">
<Button <Button
variant="destructive" variant="destructive"
@@ -3,7 +3,6 @@ import type { StorageCleanerOptions } from '@/types/storage';
import OptionItem from './OptionItem'; import OptionItem from './OptionItem';
import { useI18n } from '@/utils/chromeI18n'; import { useI18n } from '@/utils/chromeI18n';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
// 1. 引入官方标准的 Checkbox 原子组件
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
@@ -51,12 +50,9 @@ export default function StorageOptionsGrid({
)} )}
{...props} {...props}
> >
{/* 核心网格区 */}
<div className="p-3"> <div className="p-3">
{/* 💡 优化点:加入 items-stretch,确保左右卡片高度绝对对齐 */}
<div className="grid grid-cols-2 gap-2.5 items-stretch"> <div className="grid grid-cols-2 gap-2.5 items-stretch">
{optionKeys.map(({ key, isCount }) => ( {optionKeys.map(({ key, isCount }) => (
/* 💡 终极修复:直接把 key 挂在 OptionItem 上,移除了无意义的包裹 div */
<OptionItem <OptionItem
key={key} key={key}
labelKey={`storageCleaner:options.${key}`} labelKey={`storageCleaner:options.${key}`}
@@ -69,10 +65,6 @@ export default function StorageOptionsGrid({
</div> </div>
</div> </div>
{/* 3. 全选功能底护栏超进化:
- 整体赋予 cursor-pointer 和 onClick,点击一整行都能触发全选。
- 悬停时自动变色提示可点击 (hover:bg-muted/50)。
*/}
<div <div
onClick={handleToggleAll} onClick={handleToggleAll}
className="border-t border-border flex justify-between items-center px-4 py-2.5 bg-muted/20 hover:bg-muted/50 cursor-pointer select-none" className="border-t border-border flex justify-between items-center px-4 py-2.5 bg-muted/20 hover:bg-muted/50 cursor-pointer select-none"
@@ -81,11 +73,6 @@ export default function StorageOptionsGrid({
{t('storageCleaner:selectAll')} {t('storageCleaner:selectAll')}
</Label> </Label>
{/* 4. 降维打击:调用标准的 shadcn/ui Checkbox
- 阻止冒泡:防止事件重复触发。
- 完美注入半选逻辑:当 allSelected 为 false 但 someSelected 为 true 时,
组件会自动呈现优雅的 "—" (减号) 半选视觉状态,向主流系统控制台高标准看齐!
*/}
<Checkbox <Checkbox
checked={allSelected ? true : someSelected ? 'indeterminate' : false} checked={allSelected ? true : someSelected ? 'indeterminate' : false}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
-10
View File
@@ -16,7 +16,6 @@ const LiveClock = React.memo(({ unit, onUseNow, className, ...props }: LiveClock
const { showMessage } = useSnackbar(); const { showMessage } = useSnackbar();
const onUseNowRef = useRef(onUseNow); const onUseNowRef = useRef(onUseNow);
// 1. 采用毫秒/秒的双态原子计数,避免无意义的重绘
const [currentDisplay, setCurrentDisplay] = useState(() => { const [currentDisplay, setCurrentDisplay] = useState(() => {
const initNow = Date.now(); const initNow = Date.now();
return { return {
@@ -36,21 +35,17 @@ const LiveClock = React.memo(({ unit, onUseNow, className, ...props }: LiveClock
const rightNow = Date.now(); const rightNow = Date.now();
const nextText = String(Math.floor(rightNow / (unit === 'ms' ? 1 : 1000))); const nextText = String(Math.floor(rightNow / (unit === 'ms' ? 1 : 1000)));
// 性能核心:只有当生成的文本内容发生变化时,才触发 React 的 State 更新。
// 在“秒(s)”单位下,这可以让组件的渲染频率暴跌 90%,做到极度省电和高性能。
setCurrentDisplay((prev) => { setCurrentDisplay((prev) => {
if (prev.text === nextText) return prev; if (prev.text === nextText) return prev;
return { rawTime: rightNow, text: nextText }; return { rawTime: rightNow, text: nextText };
}); });
}; };
// 200ms 的高速低延迟轮询,比 1000ms 更具响应灵敏度,且因为上面有过滤,完全不用担心引发性能损耗
const tickId = setInterval(tick, 200); const tickId = setInterval(tick, 200);
return () => clearInterval(tickId); return () => clearInterval(tickId);
}, [unit]); }, [unit]);
const handleUseNow = useCallback(() => { const handleUseNow = useCallback(() => {
// 捕获真实极其精准的绝对时间戳
onUseNowRef.current(currentDisplay.rawTime); onUseNowRef.current(currentDisplay.rawTime);
showMessage?.(t('timestamp:usedSuccess'), { severity: 'success' }); showMessage?.(t('timestamp:usedSuccess'), { severity: 'success' });
}, [currentDisplay.rawTime, showMessage, t]); }, [currentDisplay.rawTime, showMessage, t]);
@@ -58,9 +53,6 @@ const LiveClock = React.memo(({ unit, onUseNow, className, ...props }: LiveClock
return ( return (
<div <div
className={cn( className={cn(
// 3. 完美适配 shadcn 暗黑模式:
// 不再写死 bg-primary/10,改用更高级的 bg-secondary/50 和中性边框,
// 在任何主题色下都能表现得低调且极具质感。
'flex items-center gap-3 px-3 h-10 rounded-lg border border-border/80 bg-secondary/50', 'flex items-center gap-3 px-3 h-10 rounded-lg border border-border/80 bg-secondary/50',
className, className,
)} )}
@@ -70,12 +62,10 @@ const LiveClock = React.memo(({ unit, onUseNow, className, ...props }: LiveClock
{t('timestamp:currentTs')} {t('timestamp:currentTs')}
</span> </span>
{/* 4. tabular-nums 强制使用等宽数字布局,彻底消灭数字跳动时字符宽度不同带来的抖动颤噪感 */}
<span className="flex-1 font-mono font-bold text-foreground text-sm tracking-tight leading-none truncate tabular-nums"> <span className="flex-1 font-mono font-bold text-foreground text-sm tracking-tight leading-none truncate tabular-nums">
{currentDisplay.text} {currentDisplay.text}
</span> </span>
{/* 5. 按钮重构成精巧的 shadcn 原子微动效风格 */}
<button <button
type="button" type="button"
onClick={handleUseNow} onClick={handleUseNow}
-10
View File
@@ -67,11 +67,6 @@ const ResultView = React.memo(
{t('timestamp:resultLabel')} {t('timestamp:resultLabel')}
</span> </span>
{/*
2. 核心结果大卡片:
对齐 shadcn 官方卡片风格,使用 bg-card、border-border 构筑多层级阴影。
核心数值直接拉粗为 text-foreground (在黑夜模式下会自动转为大气的纯白,完美避开刺眼强光)
*/}
<div className="bg-card text-card-foreground border border-border p-4 sm:p-5 rounded-xl relative mb-3.5 shadow-sm flex justify-between items-center gap-4 focus-within:ring-1 focus-within:ring-ring"> <div className="bg-card text-card-foreground border border-border p-4 sm:p-5 rounded-xl relative mb-3.5 shadow-sm flex justify-between items-center gap-4 focus-within:ring-1 focus-within:ring-ring">
<span className="font-mono font-extrabold text-foreground break-all text-xl sm:text-2xl tracking-tight leading-tight select-all tabular-nums"> <span className="font-mono font-extrabold text-foreground break-all text-xl sm:text-2xl tracking-tight leading-tight select-all tabular-nums">
{result} {result}
@@ -83,11 +78,6 @@ const ResultView = React.memo(
/> />
</div> </div>
{/*
3. 衍生的附加参考数据区:
背景改为低饱和度的 bg-muted/40 隔离带。
内部数值降级为 text-muted-foreground,建立教科书般的完美“视觉权重层级”。
*/}
<div className="bg-muted/40 p-4 rounded-xl border border-border/50 flex flex-col gap-3"> <div className="bg-muted/40 p-4 rounded-xl border border-border/50 flex flex-col gap-3">
{[ {[
{ label: t('timestamp:relativeTime'), value: extraInfo?.relative }, { label: t('timestamp:relativeTime'), value: extraInfo?.relative },
-24
View File
@@ -6,7 +6,6 @@ import { useTimestampConverter } from './useTimestampConverter';
import { useI18n } from '@/utils/chromeI18n'; import { useI18n } from '@/utils/chromeI18n';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
// 1. 引入标准的 shadcn/ui 原子表单组件(代替原生的原生 Input 和 Select
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { import {
Select, Select,
@@ -19,7 +18,6 @@ import {
export default function Index() { export default function Index() {
const { t } = useI18n('timestamp'); const { t } = useI18n('timestamp');
// 2. 完美对接全新重构后的统一单源响应式 Hook
const { const {
mode, mode,
input, input,
@@ -37,15 +35,11 @@ export default function Index() {
return ( return (
<div className="min-h-screen bg-background text-foreground antialiased selection:bg-primary/20"> <div className="min-h-screen bg-background text-foreground antialiased selection:bg-primary/20">
<div className="max-w-7xl mx-auto p-4 sm:p-6 space-y-4"> <div className="max-w-7xl mx-auto p-4 sm:p-6 space-y-4">
{/* 动态参考信息时钟条 */}
<LiveClock unit={unit} onUseNow={handleUseNow} /> <LiveClock unit={unit} onUseNow={handleUseNow} />
{/* 核心工作台网格:桌面端 md+ 左右等宽分栏;移动端单栏堆叠 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-stretch"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-stretch">
{/* 左栏:转换工作台 */}
<div className="p-5 rounded-xl border border-border bg-card text-card-foreground shadow-sm flex flex-col justify-between"> <div className="p-5 rounded-xl border border-border bg-card text-card-foreground shadow-sm flex flex-col justify-between">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{/* 模式选择切换组 */}
<SwitchButtonGroup <SwitchButtonGroup
value={mode} value={mode}
options={[ options={[
@@ -56,12 +50,7 @@ export default function Index() {
size="small" size="small"
/> />
{/* 输入交互区 */}
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
{/*
3. 替换为标准的 shadcn <Input /> 组件:
享受原生高水准的 focus-visible 环形动画响应。
*/}
<Input <Input
type="text" type="text"
placeholder={ placeholder={
@@ -75,13 +64,10 @@ export default function Index() {
)} )}
/> />
{/* 错误自愈提示 */}
{error && <p className="text-destructive text-xs font-medium px-0.5">{error}</p>} {error && <p className="text-destructive text-xs font-medium px-0.5">{error}</p>}
</div> </div>
{/* 核心配置群:单位切换 + 时区选择紧凑横排 */}
<div className="flex flex-col sm:flex-row items-stretch gap-3 w-full"> <div className="flex flex-col sm:flex-row items-stretch gap-3 w-full">
{/* 时间精度单位选择 */}
<SwitchButtonGroup <SwitchButtonGroup
value={unit} value={unit}
options={[ options={[
@@ -93,10 +79,6 @@ export default function Index() {
className="sm:w-auto shrink-0" // 窄屏下全宽,宽屏下自适应收缩 className="sm:w-auto shrink-0" // 窄屏下全宽,宽屏下自适应收缩
/> />
{/*
4. 降维打击:将原生 <select> 强行超进化为标准的 shadcn <Select>
全操作系统的样式绝对一致,完美融合暗黑模式,悬浮弹窗自带微距磨砂玻璃阻尼动效。
*/}
<Select value={zone} onValueChange={(v: string) => setZone(v as typeof zone)}> <Select value={zone} onValueChange={(v: string) => setZone(v as typeof zone)}>
<SelectTrigger className="flex-1 font-mono font-semibold h-9 shadow-sm bg-background"> <SelectTrigger className="flex-1 font-mono font-semibold h-9 shadow-sm bg-background">
<SelectValue placeholder="选择时区" /> <SelectValue placeholder="选择时区" />
@@ -115,14 +97,8 @@ export default function Index() {
</Select> </Select>
</div> </div>
</div> </div>
{/*
5. 彻底移除了原先丑陋的手动 convert 按钮!
在响应式 Hook 的加持下,此处留白或由排版自然撑开,界面视觉极其干净。
*/}
</div> </div>
{/* 右栏:结果实时流展示卡片 */}
<div className="p-5 rounded-xl border border-border bg-card text-card-foreground shadow-sm h-full flex flex-col"> <div className="p-5 rounded-xl border border-border bg-card text-card-foreground shadow-sm h-full flex flex-col">
<ResultView result={result} mode={mode} unit={unit} zone={zone} showEmptyPlaceholder /> <ResultView result={result} mode={mode} unit={unit} zone={zone} showEmptyPlaceholder />
</div> </div>
@@ -31,11 +31,8 @@ export function useTimestampConverter(): UseTimestampConverterReturn {
const [unit, setUnit] = useState<UnitType>('ms'); const [unit, setUnit] = useState<UnitType>('ms');
const [zone, setZone] = useState<ZoneType>('Asia/Shanghai'); const [zone, setZone] = useState<ZoneType>('Asia/Shanghai');
// 1. 唯一受控源:不再区分 ts/dt,输入框在当前模式下展现的就是它的值
const [input, setInput] = useState(() => String(Date.now())); const [input, setInput] = useState(() => String(Date.now()));
// 2. 核心魔法:利用 useMemo 达成 0 延迟响应式转换 (Reactive Pipeline)
// 只要 input, mode, unit, zone 任何一个发生改变,结果和错误信息自动流出,废除 convert 按钮
const conversionPipeline = useMemo(() => { const conversionPipeline = useMemo(() => {
const rawInput = input.trim(); const rawInput = input.trim();
if (!rawInput) return { result: '', error: '' }; if (!rawInput) return { result: '', error: '' };
@@ -63,7 +60,6 @@ export function useTimestampConverter(): UseTimestampConverterReturn {
const { result, error } = conversionPipeline; const { result, error } = conversionPipeline;
// 3. 处理右键菜单联动(直接修改唯一的 input,衍生转换自动触发)
const handleContextMenuData = useCallback((payload: string) => { const handleContextMenuData = useCallback((payload: string) => {
const trimmed = payload.trim(); const trimmed = payload.trim();
if (isTimestampLike(trimmed)) { if (isTimestampLike(trimmed)) {
@@ -96,8 +92,6 @@ export function useTimestampConverter(): UseTimestampConverterReturn {
[mode, unit, zone], [mode, unit, zone],
); );
// 4. 尊享级连贯交互:切换 Mode 时,自动把上一个模式计算出的结果喂进输入框
// 比如:输入时间戳 -> 得到日期 -> 切换模式 -> 日期直接进入输入框,方便用户进行微调反向转换
const handleSetMode = useCallback( const handleSetMode = useCallback(
(newMode: 'ts2dt' | 'dt2ts') => { (newMode: 'ts2dt' | 'dt2ts') => {
setMode(newMode); setMode(newMode);
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ThemeModeProvider } from './ThemeModeProvider'; import { ThemeModeProvider } from './ThemeModeProvider';
import { RouterProvider } from './RouterProvider'; // 💡 1. 全局合龙:引入满血重构的路由中枢 import { RouterProvider } from './RouterProvider';
interface AppRootProps { interface AppRootProps {
children: React.ReactNode; children: React.ReactNode;
+5 -6
View File
@@ -7,7 +7,7 @@ import {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { browser } from 'wxt/browser'; // 💡 1. 规范回归:引入 WXT 标准多端一致性代理空间 import { browser } from 'wxt/browser';
import type { ContextMenuPendingData, PageType, StorageSchema } from '@/types/storage'; import type { ContextMenuPendingData, PageType, StorageSchema } from '@/types/storage';
import { storageUtil } from '@/utils/chromeStorage'; import { storageUtil } from '@/utils/chromeStorage';
import { import {
@@ -96,7 +96,7 @@ export function RouterProvider({
visiblePagesKey = 'app/visiblePages', visiblePagesKey = 'app/visiblePages',
pageOrderKey = 'app/pageOrder', pageOrderKey = 'app/pageOrder',
}: RouterProviderProps) { }: RouterProviderProps) {
// 1. 初始化派生状态流(0 延迟快照同步) // 1. Initialize state with sync snapshot
const [currentPage, setCurrentPage] = useState<PageType>(() => const [currentPage, setCurrentPage] = useState<PageType>(() =>
getSyncSnapshot(syncKey as string, defaultRoute, isValidPage), getSyncSnapshot(syncKey as string, defaultRoute, isValidPage),
); );
@@ -153,7 +153,7 @@ export function RouterProvider({
} }
}, [defaultRoute, syncKey, syncRoute, visiblePagesKey, pageOrderKey]); }, [defaultRoute, syncKey, syncRoute, visiblePagesKey, pageOrderKey]);
// 副作用 1:组件挂载时,激活初始路由校验与右键菜单传递载荷(Context Payload)的嗅探 // Load initial data from async storage on mount
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
@@ -162,7 +162,7 @@ export function RouterProvider({
.then(() => { .then(() => {
if (cancelled) return; if (cancelled) return;
// 检查 URL 参数中的右键菜单高阶中转数据 // Check URL params for context menu data
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const feature = params.get('feature') as PageType | null; const feature = params.get('feature') as PageType | null;
@@ -182,7 +182,7 @@ export function RouterProvider({
} }
} }
// 检查 storage 中的右键菜单待处理数据(针对 openPopup 的闭环场景) // Check storage for pending context menu data
storageUtil storageUtil
.get('contextMenu/pendingData', undefined) .get('contextMenu/pendingData', undefined)
.then((pendingData) => { .then((pendingData) => {
@@ -276,7 +276,6 @@ export function RouterProvider({
} }
}; };
// 💡 修复点:全域绑定 WXT 跨浏览器代理监听器,彻底闭环多端多进程广播
browser.storage.onChanged.addListener(handleStorageChange); browser.storage.onChanged.addListener(handleStorageChange);
return () => { return () => {
browser.storage.onChanged.removeListener(handleStorageChange); browser.storage.onChanged.removeListener(handleStorageChange);
+5 -12
View File
@@ -7,7 +7,7 @@ import {
useMemo, useMemo,
useState, useState,
} from 'react'; } from 'react';
import { browser } from 'wxt/browser'; // 💡 1. 规范回归:引入 WXT 标准多端一致性代理,消灭裸奔的 chrome 空间 import { browser } from 'wxt/browser';
import { storageUtil } from '@/utils/chromeStorage'; import { storageUtil } from '@/utils/chromeStorage';
export type ThemeMode = 'light' | 'dark' | 'system'; export type ThemeMode = 'light' | 'dark' | 'system';
@@ -59,18 +59,15 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
mode === 'system' ? getSystemMode() : mode, mode === 'system' ? getSystemMode() : mode,
); );
// 统一的实态转换调度中枢
const updateResolved = useCallback((nextMode: ThemeMode) => { const updateResolved = useCallback((nextMode: ThemeMode) => {
setResolvedMode(nextMode === 'system' ? getSystemMode() : nextMode); setResolvedMode(nextMode === 'system' ? getSystemMode() : nextMode);
}, []); }, []);
// 主动切流触发大闸
const setMode = useCallback( const setMode = useCallback(
(next: ThemeMode) => { (next: ThemeMode) => {
setModeState(next); setModeState(next);
updateResolved(next); updateResolved(next);
// 💡 修复点:对异步微任务链追加标准的显式 void 断链安全隔离,彻底净化 Linter 悬空 Promise 警告
void storageUtil.set(THEME_MODE_KEY, next).catch((err) => { void storageUtil.set(THEME_MODE_KEY, next).catch((err) => {
console.error('[Theme Storage Error] Failed to persistent theme state:', err); console.error('[Theme Storage Error] Failed to persistent theme state:', err);
}); });
@@ -84,7 +81,7 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
[updateResolved], [updateResolved],
); );
// 副作用 1:冷启动时异步对齐来自长期存储的权威配置,自愈快照偏差 // Sync theme from storage on mount
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
storageUtil storageUtil
@@ -104,7 +101,7 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
}; };
}, [updateResolved]); }, [updateResolved]);
// 副作用 2:当处于跟随系统模式下,动态监听操作系统级别的高级霓虹换色 // Listen for system theme changes when in system mode
useEffect(() => { useEffect(() => {
if (mode !== 'system') return; if (mode !== 'system') return;
const mq = window.matchMedia('(prefers-color-scheme: dark)'); const mq = window.matchMedia('(prefers-color-scheme: dark)');
@@ -115,15 +112,12 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
return () => mq.removeEventListener('change', handler); return () => mq.removeEventListener('change', handler);
}, [mode]); }, [mode]);
// 副作用 3:跨上下文广播事件同步大闸(Popup <-> Sidepanel 联动核心) // Sync theme across extension contexts (popup <-> sidepanel)
useEffect(() => { useEffect(() => {
// 💡 2. 强类型对齐修复:对齐标准的 browser.storage.onChanged.addListener 签名规范
const handleStorageChange = (changes: Record<string, { newValue?: unknown }>) => { const handleStorageChange = (changes: Record<string, { newValue?: unknown }>) => {
if (changes[THEME_MODE_KEY]) { if (changes[THEME_MODE_KEY]) {
const next = changes[THEME_MODE_KEY].newValue; const next = changes[THEME_MODE_KEY].newValue;
if (isValidMode(next)) { if (isValidMode(next)) {
// 💡 3. 架构解耦:改用函数式状态更新,彻底将本 Effect 从当前的 [mode] 依赖中解脱出来!
// 这能保证多视口监听器在初始化时【只注册一次】,彻底消灭高频切流时的事件撕裂与重复注销。
setModeState((currentMode) => { setModeState((currentMode) => {
if (next !== currentMode) { if (next !== currentMode) {
updateResolved(next); updateResolved(next);
@@ -139,9 +133,8 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
return () => { return () => {
browser.storage.onChanged.removeListener(handleStorageChange); browser.storage.onChanged.removeListener(handleStorageChange);
}; };
}, [updateResolved]); // 👈 完美剔除 mode 脏依赖! }, [updateResolved]);
// 副作用 4:宿主 DOM 的 class 样式原子映射注入(一帧直出)
useEffect(() => { useEffect(() => {
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
document.documentElement.classList.toggle('dark', resolvedMode === 'dark'); document.documentElement.classList.toggle('dark', resolvedMode === 'dark');
-1
View File
@@ -48,7 +48,6 @@ export function useI18n(namespace?: string | string[]) {
} }
} }
// 处理插值
if (options) { if (options) {
for (const [placeholder, value] of Object.entries(options)) { for (const [placeholder, value] of Object.entries(options)) {
message = message.replace(`{{${placeholder}}}`, String(value)); message = message.replace(`{{${placeholder}}}`, String(value));
-2
View File
@@ -41,7 +41,6 @@ export function htmlToMarkdown(html: string): HtmlToMarkdownResult {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(trimmed, 'text/html'); const doc = parser.parseFromString(trimmed, 'text/html');
// 检查解析错误
const parserError = doc.querySelector('parsererror'); const parserError = doc.querySelector('parsererror');
if (parserError) { if (parserError) {
return { return {
@@ -225,7 +224,6 @@ function convertTable(table: HTMLElement): string {
const lines: string[] = []; const lines: string[] = [];
// 处理表头
const headerRow = rows[0]; const headerRow = rows[0];
const headerCells = Array.from(headerRow.querySelectorAll('th, td')); const headerCells = Array.from(headerRow.querySelectorAll('th, td'));
const headers = headerCells.map((cell) => (cell.textContent ?? '').trim()); const headers = headerCells.map((cell) => (cell.textContent ?? '').trim());