refactor: 删除高优先级死代码
删除未使用的代码以提高代码质量和可维护性: 删除的文件: - src/pages/Dashboard/ToolCard.tsx:未被使用的组件 - src/utils/useDebounce.ts:未被使用的 hook - src/components/__tests__/ToolCard.test.tsx:对应组件的测试文件 删除的导出函数: - chromeTabs: getActiveTab, getActiveTabDomain, ensureContentScriptInjected - clipboard: copyImageToClipboard - storageCleaner: isEmptyResult - chromeI18n: preloadNamespaces - useContextMenuData: clearContextMenuData 更新了对应的测试文件以保持一致性 总计删除 525 行代码,所有 473 个测试通过
This commit is contained in:
@@ -1,118 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
||||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import ToolCard from '@/pages/Dashboard/ToolCard';
|
|
||||||
import { Clock } from 'lucide-react';
|
|
||||||
|
|
||||||
describe('ToolCard 组件', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('渲染测试', () => {
|
|
||||||
it('应渲染标题和描述', () => {
|
|
||||||
render(
|
|
||||||
<ToolCard
|
|
||||||
title="测试工具"
|
|
||||||
description="这是一个测试工具"
|
|
||||||
colorKey="primary"
|
|
||||||
icon={Clock}
|
|
||||||
onNavigate={() => {}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText('测试工具')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('这是一个测试工具')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('无描述时仅渲染标题', () => {
|
|
||||||
render(<ToolCard title="仅标题" colorKey="primary" icon={Clock} onNavigate={() => {}} />);
|
|
||||||
|
|
||||||
expect(screen.getByText('仅标题')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应渲染图标', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ToolCard title="带图标" colorKey="primary" icon={Clock} onNavigate={() => {}} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const svgElement = container.querySelector('svg');
|
|
||||||
expect(svgElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('提供快照内容时应渲染快照', () => {
|
|
||||||
render(
|
|
||||||
<ToolCard
|
|
||||||
title="带快照"
|
|
||||||
colorKey="primary"
|
|
||||||
icon={Clock}
|
|
||||||
onNavigate={() => {}}
|
|
||||||
snapshot={<div data-testid="snapshot">快照内容</div>}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('snapshot')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('未提供快照时不渲染快照区域', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ToolCard
|
|
||||||
title="无快照"
|
|
||||||
colorKey="primary"
|
|
||||||
icon={Clock}
|
|
||||||
onClick={() => {}}
|
|
||||||
onNavigate={function (): void {
|
|
||||||
throw new Error('Function not implemented.');
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.querySelector('[data-testid="snapshot"]')).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应使用 CardActionArea 渲染,支持键盘聚焦', () => {
|
|
||||||
render(<ToolCard title="可聚焦" colorKey="primary" icon={Clock} onNavigate={() => {}} />);
|
|
||||||
|
|
||||||
const button = screen.getByRole('button', { name: /可聚焦/ });
|
|
||||||
expect(button).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('交互测试', () => {
|
|
||||||
it('点击时应调用 onClick', () => {
|
|
||||||
const handleClick = vi.fn();
|
|
||||||
render(<ToolCard title="可点击" colorKey="primary" icon={Clock} onNavigate={handleClick} />);
|
|
||||||
|
|
||||||
const button = screen.getByRole('button', { name: /可点击/ });
|
|
||||||
fireEvent.click(button);
|
|
||||||
|
|
||||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('按 Enter 键时应调用 onClick', async () => {
|
|
||||||
const handleClick = vi.fn();
|
|
||||||
render(
|
|
||||||
<ToolCard title="键盘可触发" colorKey="primary" icon={Clock} onNavigate={handleClick} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const button = screen.getByRole('button', { name: /键盘可触发/ });
|
|
||||||
await act(async () => {
|
|
||||||
button.focus();
|
|
||||||
await userEvent.keyboard('{Enter}');
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('样式测试', () => {
|
|
||||||
it('应应用自定义颜色代码', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ToolCard title="自定义颜色" colorKey="warning" icon={Clock} onNavigate={() => {}} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const svgElement = container.querySelector('svg');
|
|
||||||
expect(svgElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import type { ComponentType } from 'react';
|
|
||||||
import React from 'react';
|
|
||||||
import type { LucideProps } from 'lucide-react';
|
|
||||||
import { ChevronRight } from 'lucide-react';
|
|
||||||
import type { PaletteColorKey } from '@/config/features';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
const PALETTE_COLORS: Record<PaletteColorKey, string> = {
|
|
||||||
primary: '13, 148, 136', // teal
|
|
||||||
success: '22, 163, 74', // green
|
|
||||||
warning: '217, 119, 6', // amber (存储清理的橙色轴)
|
|
||||||
error: '220, 38, 38', // red
|
|
||||||
secondary: '147, 51, 232',
|
|
||||||
info: '37, 99, 235', // blue
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ToolCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
snapshot?: React.ReactNode;
|
|
||||||
colorKey: PaletteColorKey;
|
|
||||||
icon: ComponentType<LucideProps>;
|
|
||||||
onNavigate: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ToolCard({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
snapshot,
|
|
||||||
colorKey,
|
|
||||||
icon: IconComponent,
|
|
||||||
onNavigate,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: ToolCardProps) {
|
|
||||||
const rgbValues = PALETTE_COLORS[colorKey];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
['--tool-color' as string]: rgbValues,
|
|
||||||
}}
|
|
||||||
/* 💡 核心修复点:
|
|
||||||
- 坚决不用 h-full 或固定高度,锁死 h-auto(高度自适应流),配合 py-4 px-4 牢牢把内容包裹在卡片体内。
|
|
||||||
- 废除原先会乱飘的内联 style 属性擦写,全权放权给 Tailwind 的声明式 hover 变体。
|
|
||||||
*/
|
|
||||||
className={cn(
|
|
||||||
'group relative rounded-xl border border-border/70 bg-card text-card-foreground p-4 h-auto flex flex-col items-stretch justify-start gap-3 shadow-sm select-none box-border',
|
|
||||||
'hover:bg-muted/30',
|
|
||||||
'hover:border-[rgba(var(--tool-color),0.45)]',
|
|
||||||
'hover:shadow-[0_8px_24px_-8px_rgba(var(--tool-color),0.14)] dark:hover:shadow-[0_8px_30px_-10px_rgba(var(--tool-color),0.25)]',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{/* 上半部分:核心信息交互排版轴 */}
|
|
||||||
<div className="flex items-center justify-between w-full relative min-w-0 min-h-[44px]">
|
|
||||||
<div className="flex gap-3 items-center min-w-0 flex-1 pr-2">
|
|
||||||
{/* 左侧圆形图标容器 */}
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'flex items-center justify-center w-10 h-10 rounded-xl shrink-0',
|
|
||||||
'bg-[rgba(var(--tool-color),0.08)] dark:bg-[rgba(var(--tool-color),0.12)]',
|
|
||||||
'text-[rgb(var(--tool-color))]',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<IconComponent className="h-5 w-5 shrink-0" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 中间文字描述区:利用 flex-1 min-w-0 防御文本过长发生恶性撑开 */}
|
|
||||||
<div className="flex-1 min-w-0 flex flex-col">
|
|
||||||
<h4 className="font-bold text-sm tracking-tight text-foreground leading-snug">
|
|
||||||
{title}
|
|
||||||
</h4>
|
|
||||||
{description && (
|
|
||||||
<p className="text-[11px] font-medium text-muted-foreground/90 mt-0.5 leading-normal w-full truncate">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 右侧指示小箭头 */}
|
|
||||||
<div className="text-muted-foreground/40 group-hover:text-[rgb(var(--tool-color))] p-1 shrink-0 group-hover:translate-x-0.5">
|
|
||||||
<ChevronRight className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 覆盖整个上半部分的绝对定位隐形跳转层(A11y 无障碍标准合规) */}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onNavigate}
|
|
||||||
aria-label={`进入 ${title}`}
|
|
||||||
className="absolute inset-0 w-full h-full cursor-pointer bg-transparent border-none opacity-0 focus-visible:outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 下半部分:未来的动态预览沙箱独立承载区 */}
|
|
||||||
{snapshot != null && (
|
|
||||||
<div className="mt-1 pt-3 border-t border-dashed border-border/80 w-full relative z-10 select-text">
|
|
||||||
{snapshot}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolCard.displayName = 'ToolCard';
|
|
||||||
@@ -1,101 +1,7 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import {
|
import { openExtensionPage } from '@/utils/chromeTabs';
|
||||||
getActiveTab,
|
|
||||||
getActiveTabDomain,
|
|
||||||
openExtensionPage,
|
|
||||||
ensureContentScriptInjected,
|
|
||||||
} from '@/utils/chromeTabs';
|
|
||||||
|
|
||||||
describe('chromeTabs', () => {
|
describe('chromeTabs', () => {
|
||||||
describe('getActiveTab', () => {
|
|
||||||
it('应该返回当前活动标签页', async () => {
|
|
||||||
const mockTab = { id: 1, url: 'https://example.com', title: 'Example' } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await getActiveTab();
|
|
||||||
|
|
||||||
expect(result).toEqual(mockTab);
|
|
||||||
expect(chrome.tabs.query).toHaveBeenCalledWith({ active: true, currentWindow: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当没有活动标签页时应返回 null', async () => {
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([]);
|
|
||||||
|
|
||||||
const result = await getActiveTab();
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当查询失败时应返回 null 并记录错误', async () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
(chrome.tabs.query as any).mockRejectedValue(new Error('Permission denied'));
|
|
||||||
|
|
||||||
const result = await getActiveTab();
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith('获取活动标签页失败:', expect.any(Error));
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getActiveTabDomain', () => {
|
|
||||||
it('应该返回当前活动标签页的域名', async () => {
|
|
||||||
const mockTab = { id: 1, url: 'https://example.com/path?query=1' } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await getActiveTabDomain();
|
|
||||||
|
|
||||||
expect(result).toBe('example.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该处理带有端口的 URL', async () => {
|
|
||||||
const mockTab = { id: 1, url: 'https://example.com:8080/path' } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await getActiveTabDomain();
|
|
||||||
|
|
||||||
expect(result).toBe('example.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当标签页没有 URL 时应返回空字符串', async () => {
|
|
||||||
const mockTab = { id: 1 } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await getActiveTabDomain();
|
|
||||||
|
|
||||||
expect(result).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当没有活动标签页时应返回空字符串', async () => {
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([]);
|
|
||||||
|
|
||||||
const result = await getActiveTabDomain();
|
|
||||||
|
|
||||||
expect(result).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当 URL 解析失败时应返回空字符串并记录错误', async () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
const mockTab = { id: 1, url: 'not-a-valid-url' } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await getActiveTabDomain();
|
|
||||||
|
|
||||||
expect(result).toBe('');
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith('解析域名失败:', expect.any(Error));
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该处理 chrome-extension URL', async () => {
|
|
||||||
const mockTab = { id: 1, url: 'chrome-extension://abc123/popup.html' } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await getActiveTabDomain();
|
|
||||||
|
|
||||||
expect(result).toBe('abc123');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('openExtensionPage', () => {
|
describe('openExtensionPage', () => {
|
||||||
it('应该在新标签页中打开扩展页面', async () => {
|
it('应该在新标签页中打开扩展页面', async () => {
|
||||||
await openExtensionPage('popup.html');
|
await openExtensionPage('popup.html');
|
||||||
@@ -116,44 +22,4 @@ describe('chromeTabs', () => {
|
|||||||
consoleSpy.mockRestore();
|
consoleSpy.mockRestore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ensureContentScriptInjected', () => {
|
|
||||||
it('当存在活动标签页时应返回 true', async () => {
|
|
||||||
const mockTab = { id: 123, url: 'https://example.com' } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await ensureContentScriptInjected();
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当没有活动标签页时应返回 false', async () => {
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([]);
|
|
||||||
|
|
||||||
const result = await ensureContentScriptInjected();
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当标签页没有 id 时应返回 false', async () => {
|
|
||||||
const mockTab = { url: 'https://example.com' } as chrome.tabs.Tab;
|
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
|
||||||
|
|
||||||
const result = await ensureContentScriptInjected();
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当整体操作失败时应返回 false 并记录错误', async () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
(chrome.tabs.query as any).mockRejectedValue(new Error('Query failed'));
|
|
||||||
|
|
||||||
const result = await ensureContentScriptInjected();
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
// getActiveTab catches the error and logs "获取活动标签页失败"
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith('获取活动标签页失败:', expect.any(Error));
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
import { describe, expect, it, vi, beforeAll } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { copyTextToClipboard, copyImageToClipboard } from '@/utils/clipboard';
|
import { copyTextToClipboard } from '@/utils/clipboard';
|
||||||
|
|
||||||
// Mock ClipboardItem for test environment
|
|
||||||
class MockClipboardItem {
|
|
||||||
constructor(public items: Record<string, Blob>) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
(globalThis as any).ClipboardItem = MockClipboardItem;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('clipboard', () => {
|
describe('clipboard', () => {
|
||||||
describe('copyTextToClipboard', () => {
|
describe('copyTextToClipboard', () => {
|
||||||
@@ -31,27 +22,4 @@ describe('clipboard', () => {
|
|||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('copyImageToClipboard', () => {
|
|
||||||
it('复制成功时应返回 true', async () => {
|
|
||||||
const write = vi.fn().mockResolvedValue(undefined);
|
|
||||||
Object.assign(navigator, { clipboard: { write } });
|
|
||||||
|
|
||||||
const blob = new Blob(['png data'], { type: 'image/png' });
|
|
||||||
const result = await copyImageToClipboard(blob);
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
expect(write).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('复制失败时应返回 false', async () => {
|
|
||||||
const write = vi.fn().mockRejectedValue(new Error('Permission denied'));
|
|
||||||
Object.assign(navigator, { clipboard: { write } });
|
|
||||||
|
|
||||||
const blob = new Blob(['png data'], { type: 'image/png' });
|
|
||||||
const result = await copyImageToClipboard(blob);
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import { renderHook, act } from '@testing-library/react';
|
import { renderHook, act } from '@testing-library/react';
|
||||||
import {
|
import { useContextMenuData, saveContextMenuData } from '@/utils/useContextMenuData';
|
||||||
useContextMenuData,
|
|
||||||
saveContextMenuData,
|
|
||||||
clearContextMenuData,
|
|
||||||
} from '@/utils/useContextMenuData';
|
|
||||||
|
|
||||||
describe('useContextMenuData', () => {
|
describe('useContextMenuData', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -48,14 +44,6 @@ describe('useContextMenuData', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('clearContextMenuData', () => {
|
|
||||||
it('应该从 storage 中删除数据', async () => {
|
|
||||||
await clearContextMenuData();
|
|
||||||
|
|
||||||
expect(chrome.storage.local.remove).toHaveBeenCalledWith(['contextMenu/pendingData']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('useContextMenuData Hook', () => {
|
describe('useContextMenuData Hook', () => {
|
||||||
it('当 storage 中有匹配数据时应调用 onData 回调', async () => {
|
it('当 storage 中有匹配数据时应调用 onData 回调', async () => {
|
||||||
const mockData = {
|
const mockData = {
|
||||||
|
|||||||
@@ -66,11 +66,3 @@ export function useI18n(namespace?: string | string[]) {
|
|||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 预加载命名空间(无操作,兼容 useLazyTranslation)
|
|
||||||
*/
|
|
||||||
export async function preloadNamespaces(_namespaces: string[]): Promise<void> {
|
|
||||||
// chrome.i18n 是同步的,无需预加载
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,35 +2,6 @@
|
|||||||
* Chrome 标签页相关工具函数
|
* Chrome 标签页相关工具函数
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前活动的标签页
|
|
||||||
*/
|
|
||||||
export async function getActiveTab(): Promise<chrome.tabs.Tab | null> {
|
|
||||||
try {
|
|
||||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
||||||
return tab || null;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取活动标签页失败:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前活动的标签页域名
|
|
||||||
*/
|
|
||||||
export async function getActiveTabDomain(): Promise<string> {
|
|
||||||
const tab = await getActiveTab();
|
|
||||||
if (tab?.url) {
|
|
||||||
try {
|
|
||||||
const url = new URL(tab.url);
|
|
||||||
return url.hostname;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('解析域名失败:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在新标签页中打开扩展页面
|
* 在新标签页中打开扩展页面
|
||||||
* @param page - 扩展页面路径(如 'popup.html')
|
* @param page - 扩展页面路径(如 'popup.html')
|
||||||
@@ -50,28 +21,3 @@ export async function openExtensionPage(
|
|||||||
console.error('打开扩展页面失败:', error);
|
console.error('打开扩展页面失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 确保内容脚本已注入
|
|
||||||
*/
|
|
||||||
export async function ensureContentScriptInjected(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const tab = await getActiveTab();
|
|
||||||
if (!tab?.id) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
console.log('内容脚本未注入,尝试注入...');
|
|
||||||
console.error('注入内容脚本失败:', e);
|
|
||||||
await chrome.scripting.executeScript({
|
|
||||||
target: { tabId: tab.id },
|
|
||||||
files: ['/content-scripts/content.js'],
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('注入内容脚本失败:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,21 +11,3 @@ export async function copyTextToClipboard(text: string): Promise<boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 复制图片到剪贴板
|
|
||||||
* @param blob 要复制的图片
|
|
||||||
* @returns Promise<boolean> 是否复制成功
|
|
||||||
*/
|
|
||||||
export async function copyImageToClipboard(blob: Blob): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.write([
|
|
||||||
new ClipboardItem({
|
|
||||||
'image/png': blob,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -381,10 +381,3 @@ export function formatCleaningResult(
|
|||||||
|
|
||||||
return t('storageCleaner:cleanedSummary', { items: parts.join(', ') });
|
return t('storageCleaner:cleanedSummary', { items: parts.join(', ') });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEmptyResult(result: CleaningResult): boolean {
|
|
||||||
const values = Object.values(result).filter(
|
|
||||||
(r): r is StorageCleanResult => r?.success === true && r.count > 0,
|
|
||||||
);
|
|
||||||
return values.length === 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -90,10 +90,3 @@ export async function saveContextMenuData(
|
|||||||
};
|
};
|
||||||
await storageUtil.set(STORAGE_KEY, pendingData);
|
await storageUtil.set(STORAGE_KEY, pendingData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除右键菜单待处理数据
|
|
||||||
*/
|
|
||||||
export async function clearContextMenuData(): Promise<void> {
|
|
||||||
await storageUtil.remove(STORAGE_KEY);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* useDebounce Hook - 防抖值
|
|
||||||
*
|
|
||||||
* @param value - 需要防抖的值
|
|
||||||
* @param delay - 延迟时间(毫秒)
|
|
||||||
* @returns 防抖后的值
|
|
||||||
*/
|
|
||||||
export function useDebounce<T>(value: T, delay: number): T {
|
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setDebouncedValue(value);
|
|
||||||
}, delay);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
};
|
|
||||||
}, [value, delay]);
|
|
||||||
|
|
||||||
return debouncedValue;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user