refactor: 将 UI 文案改为中文并优化相关逻辑
- 在 background.ts 中将错误信息翻译为中文。 - 在 uiPopover.ts 中移除注释并更新弹出框标题。 - 在 Base64ConverterSection.tsx 中动态显示最大文件大小。 - 在 DecodeResultPaper.tsx 中简化标题文案注释。 - 在 GenerateButton.tsx 中动态显示生成进度和预计剩余时间。 - 在 base64Converter.ts 中将错误信息翻译为中文。
This commit is contained in:
@@ -65,8 +65,8 @@ export default defineBackground(() => {
|
|||||||
const tabId = sender?.tab?.id;
|
const tabId = sender?.tab?.id;
|
||||||
|
|
||||||
if (!tabId) {
|
if (!tabId) {
|
||||||
console.warn('[RightClickRestorer] Injection request missing tabId');
|
console.warn('[RightClickRestorer] 注入请求缺少 tabId');
|
||||||
return { success: false, message: 'Missing tabId' };
|
return { success: false, message: '缺少 tabId' };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -79,7 +79,7 @@ export default defineBackground(() => {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||||
console.error('[RightClickRestorer] executeScript failed:', errorMsg);
|
console.error('[RightClickRestorer] 执行脚本失败:', errorMsg);
|
||||||
return { success: false, message: errorMsg };
|
return { success: false, message: errorMsg };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -137,12 +137,11 @@ function escapeHtml(text: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function positionPopover(popover: HTMLElement, x: number, y: number): void {
|
function positionPopover(popover: HTMLElement, x: number, y: number): void {
|
||||||
// 此时借助 visibility: hidden,元素在隐藏状态下拥有真实的布局高宽
|
|
||||||
const rect = popover.getBoundingClientRect();
|
const rect = popover.getBoundingClientRect();
|
||||||
const viewportWidth = window.innerWidth;
|
const viewportWidth = window.innerWidth;
|
||||||
const viewportHeight = window.innerHeight;
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
let left = x + 8; // 微微追加水平偏置,防范直接遮挡用户的鼠标落点
|
let left = x + 8;
|
||||||
let top = y + 8;
|
let top = y + 8;
|
||||||
|
|
||||||
if (left + rect.width > viewportWidth - 16) {
|
if (left + rect.width > viewportWidth - 16) {
|
||||||
@@ -178,7 +177,7 @@ function showPopover(
|
|||||||
|
|
||||||
const titleSpan = document.createElement('span');
|
const titleSpan = document.createElement('span');
|
||||||
titleSpan.className = 'popover-title';
|
titleSpan.className = 'popover-title';
|
||||||
titleSpan.textContent = title; // ✅ 强安全性护航
|
titleSpan.textContent = title;
|
||||||
|
|
||||||
const closeBtn = document.createElement('button');
|
const closeBtn = document.createElement('button');
|
||||||
closeBtn.className = 'popover-close';
|
closeBtn.className = 'popover-close';
|
||||||
@@ -194,10 +193,9 @@ function showPopover(
|
|||||||
|
|
||||||
const contentContainer = document.createElement('div');
|
const contentContainer = document.createElement('div');
|
||||||
contentContainer.className = 'popover-content';
|
contentContainer.className = 'popover-content';
|
||||||
contentContainer.innerHTML = contentHtml; // 内部拼装的方法已提前完成全消毒转义
|
contentContainer.innerHTML = contentHtml;
|
||||||
popover.appendChild(contentContainer);
|
popover.appendChild(contentContainer);
|
||||||
|
|
||||||
// 提前移除激活类名,使 visibility: hidden 起效以供测量
|
|
||||||
popover.classList.remove('visible');
|
popover.classList.remove('visible');
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -226,7 +224,6 @@ export function hidePopover(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function showTimestampResult(x: number, y: number, timestamp: string, result: string): void {
|
export function showTimestampResult(x: number, y: number, timestamp: string, result: string): void {
|
||||||
// 对外部传来的参数先全数塞入 escapeHtml 大闸进行纯氧化清洗
|
|
||||||
const cleanTimestamp = escapeHtml(timestamp);
|
const cleanTimestamp = escapeHtml(timestamp);
|
||||||
const cleanResult = escapeHtml(result);
|
const cleanResult = escapeHtml(result);
|
||||||
|
|
||||||
@@ -236,7 +233,7 @@ export function showTimestampResult(x: number, y: number, timestamp: string, res
|
|||||||
<div class="popover-label">转换结果</div>
|
<div class="popover-label">转换结果</div>
|
||||||
<div class="popover-value">${cleanResult}</div>
|
<div class="popover-value">${cleanResult}</div>
|
||||||
`;
|
`;
|
||||||
showPopover(x, y, content, '⏰ 时间戳转换');
|
showPopover(x, y, content, '转换结果');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showTextStatsResult(
|
export function showTextStatsResult(
|
||||||
@@ -246,7 +243,6 @@ export function showTextStatsResult(
|
|||||||
stats: { characters: number; words: number; lines: number; bytes: number },
|
stats: { characters: number; words: number; lines: number; bytes: number },
|
||||||
): void {
|
): void {
|
||||||
const truncatedText = text.length > 50 ? text.substring(0, 50) + '...' : text;
|
const truncatedText = text.length > 50 ? text.substring(0, 50) + '...' : text;
|
||||||
// 对选中的脏文本先进行严格转义
|
|
||||||
const cleanText = escapeHtml(truncatedText);
|
const cleanText = escapeHtml(truncatedText);
|
||||||
|
|
||||||
const content = `
|
const content = `
|
||||||
@@ -271,5 +267,5 @@ export function showTextStatsResult(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
showPopover(x, y, content, '📊 文本统计');
|
showPopover(x, y, content, '统计结果');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
|
|||||||
setCustomFileName,
|
setCustomFileName,
|
||||||
resetAll,
|
resetAll,
|
||||||
safeFileSelect,
|
safeFileSelect,
|
||||||
|
maxFileSizeStr,
|
||||||
} = useBase64Converter({ mode });
|
} = useBase64Converter({ mode });
|
||||||
|
|
||||||
const handleDirectionChange = (next: Base64ConvertDirection) => {
|
const handleDirectionChange = (next: Base64ConvertDirection) => {
|
||||||
@@ -136,7 +137,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
|
|||||||
{mode === 'image' ? '点击或拖拽图像到此处' : '点击或拖拽文件到此处'}
|
{mode === 'image' ? '点击或拖拽图像到此处' : '点击或拖拽文件到此处'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] font-medium text-muted-foreground/60">
|
<span className="text-[10px] font-medium text-muted-foreground/60">
|
||||||
{'最大文件大小:{{max}}'}
|
{`最大文件大小:${maxFileSizeStr}`}
|
||||||
</span>
|
</span>
|
||||||
{mode === 'image' && (
|
{mode === 'image' && (
|
||||||
<span className="text-[10px] font-medium text-muted-foreground/50">
|
<span className="text-[10px] font-medium text-muted-foreground/50">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { formatBytes } from '@/utils/format';
|
import { formatBytes } from '@/utils/format';
|
||||||
|
|
||||||
interface DecodeResultPaperProps {
|
interface DecodeResultPaperProps {
|
||||||
/** 标题文案,由调用方传入 i18n key 对应的值(如 decodedFileOutput / decodedImageOutput) */
|
/** 标题文案 */
|
||||||
title: string;
|
title: string;
|
||||||
/** 解码后推断的 MIME 类型 */
|
/** 解码后推断的 MIME 类型 */
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function GenerateButton({
|
|||||||
{progress && (
|
{progress && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||||
<span>{'已生成 {{current}} / {{total}} 条'}</span>
|
<span>{`已生成 ${progress.generated} / ${progress.total} 条`}</span>
|
||||||
<span>{progress.progress}%</span>
|
<span>{progress.progress}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||||||
@@ -46,7 +46,7 @@ export default function GenerateButton({
|
|||||||
</div>
|
</div>
|
||||||
{progress.estimatedTimeLeft !== undefined && (
|
{progress.estimatedTimeLeft !== undefined && (
|
||||||
<p className="text-xs text-muted-foreground text-center">
|
<p className="text-xs text-muted-foreground text-center">
|
||||||
{'预计剩余 {{time}} 秒'}
|
{`预计剩余 ${progress.estimatedTimeLeft} 秒`}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -165,12 +165,12 @@ export function isSupportedImageExtension(fileName: string): boolean {
|
|||||||
export function fileToBase64(file: File): Promise<FileToBase64Result> {
|
export function fileToBase64(file: File): Promise<FileToBase64Result> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
reject(new Error('No file provided'));
|
reject(new Error('未提供文件'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFileSizeValid(file.size)) {
|
if (!isFileSizeValid(file.size)) {
|
||||||
reject(new Error(`File size exceeds the limit (${MAX_FILE_SIZE / 1024 / 1024} MB)`));
|
reject(new Error(`文件大小超出限制(最大 ${MAX_FILE_SIZE / 1024 / 1024} MB)`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ export function fileToBase64(file: File): Promise<FileToBase64Result> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
reader.onerror = () => {
|
reader.onerror = () => {
|
||||||
reject(new Error('Failed to read file'));
|
reject(new Error('读取文件失败'));
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
@@ -296,7 +296,7 @@ export function base64ToBlob(input: string): Base64ToBlobResult {
|
|||||||
const cleaned = prefixMatch ? trimmed.slice(prefixMatch[0].length) : trimmed;
|
const cleaned = prefixMatch ? trimmed.slice(prefixMatch[0].length) : trimmed;
|
||||||
|
|
||||||
if (!isValidBase64(cleaned)) {
|
if (!isValidBase64(cleaned)) {
|
||||||
throw new Error('Invalid Base64 string');
|
throw new Error('无效的 Base64 字符串');
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = base64ToBytes(cleaned);
|
const bytes = base64ToBytes(cleaned);
|
||||||
|
|||||||
Reference in New Issue
Block a user