Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0bb1ddbf88 |
71
README.md
71
README.md
@ -1,56 +1,85 @@
|
||||
# 丰链工具箱 Chrome 扩展
|
||||
|
||||
一个实用的 Chrome 扩展工具集合,目前包含二维码生成器和 Word 转 HTML 功能。
|
||||
一个实用的 Chrome 扩展工具集合,集成了二维码生成、Word 转 HTML、中英文翻译等实用功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 二维码生成器 🔲
|
||||
- 自动获取当前标签页 URL 并生成二维码
|
||||
- 支持手动输入文本/链接生成二维码
|
||||
- 高清晰度二维码输出
|
||||
- 生成高清晰度二维码,支持长文本
|
||||
- 即时预览生成结果
|
||||
|
||||
### Word 转 HTML 转换器 📄
|
||||
- 支持 .docx 文件转换为 HTML
|
||||
- 保留原文档格式和样式
|
||||
- 实时预览转换结果
|
||||
- 保留文档格式和样式
|
||||
- 支持下载转换后的 HTML 文件
|
||||
- 支持一键下载转换后的 HTML 文件
|
||||
- 自动处理图片和表格
|
||||
|
||||
### 中英文翻译 🔤
|
||||
- 支持 Windows(Ctrl+Q) 和 Mac(Command+Q) 快捷键激活
|
||||
- 框选任意区域中文文本进行翻译
|
||||
- 支持中英互译和繁体转换
|
||||
- 翻译结果即时显示在选区旁边
|
||||
- 点击任意位置关闭翻译结果
|
||||
|
||||
## 系统要求
|
||||
|
||||
- Chrome 88+ 或其他基于 Chromium 的浏览器
|
||||
- Windows/MacOS/Linux 系统均可运行
|
||||
|
||||
## 安装方法
|
||||
|
||||
### 从 Chrome 商店安装
|
||||
1. 访问 Chrome 网上应用店
|
||||
2. 搜索"丰链工具箱"
|
||||
3. 点击"添加至 Chrome"
|
||||
|
||||
### 开发者模式安装
|
||||
1. 下载本项目代码
|
||||
2. 打开 Chrome 浏览器,进入扩展程序页面 (chrome://extensions/)
|
||||
3. 开启"开发者模式"
|
||||
3. 开启右上角的"开发者模式"
|
||||
4. 点击"加载已解压的扩展程序"
|
||||
5. 选择项目文件夹即可完成安装
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 点击 Chrome 工具栏中的扩展图标打开工具箱
|
||||
2. 选择需要使用的功能标签页
|
||||
3. 根据需要使用相应功能
|
||||
|
||||
### 二维码生成
|
||||
- 自动显示当前页面的二维码
|
||||
- 可以在输入框中输入其他文本/链接并点击"生成二维码"
|
||||
1. 点击扩展图标打开工具箱
|
||||
2. 默认显示当前页面的二维码
|
||||
3. 可在输入框中输入任意文本/链接
|
||||
4. 点击"生成二维码"按钮即可
|
||||
|
||||
### Word 转 HTML
|
||||
- 点击选择文件上传 Word 文档
|
||||
- 等待转换完成后预览效果
|
||||
- 点击"下载 HTML"保存转换结果
|
||||
1. 点击"Word转HTML"标签页
|
||||
2. 点击选择或拖拽上传 Word 文档
|
||||
3. 等待转换完成后可预览效果
|
||||
4. 点击"下载HTML"保存文件
|
||||
|
||||
### 翻译功能
|
||||
1. 点击"翻译设置"标签页开启功能
|
||||
2. 选择目标语言(英文/繁体中文)
|
||||
3. 在任意页面按下快捷键(Ctrl+Q/Command+Q)
|
||||
4. 框选需要翻译的文本
|
||||
5. 翻译结果会显示在选区旁边
|
||||
|
||||
## 技术栈
|
||||
|
||||
- HTML5
|
||||
- CSS3
|
||||
- JavaScript
|
||||
- HTML5/CSS3
|
||||
- JavaScript ES6+
|
||||
- Chrome Extension API
|
||||
- QRCode.js
|
||||
- Mammoth.js
|
||||
- QRCode.js - 二维码生成
|
||||
- Mammoth.js - Word 文档转换
|
||||
- 百度翻译 API - 文本翻译
|
||||
|
||||
## 开发者
|
||||
|
||||
Made by Scout
|
||||
|
||||
## 许可证
|
||||
## 更新日志
|
||||
|
||||
[添加许可证类型]
|
||||
### v1.0.0
|
||||
- 初始版本发布
|
||||
- 实现基础功能:二维码生成、Word转HTML、翻译
|
||||
- 支持 Windows/Mac 快捷键
|
||||
|
342
background.js
Normal file
342
background.js
Normal file
@ -0,0 +1,342 @@
|
||||
// 存储注入状态
|
||||
let injectedTabs = new Set();
|
||||
|
||||
// 监听快捷键命令
|
||||
chrome.commands.onCommand.addListener(async (command) => {
|
||||
if (command === 'trigger-select') {
|
||||
try {
|
||||
const result = await chrome.storage.sync.get(['translateEnabled']);
|
||||
console.log('Translation enabled status:', result.translateEnabled);
|
||||
|
||||
// 只有当明确设置为 false 时才禁用
|
||||
if (result.translateEnabled === false) {
|
||||
console.log('Translation is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (tab) {
|
||||
await injectScriptsIfNeeded(tab.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking translation status:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 处理翻译请求
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === 'translate') {
|
||||
handleTranslation(request.text)
|
||||
.then(sendResponse)
|
||||
.catch(error => {
|
||||
console.error('Translation error:', error);
|
||||
sendResponse({ error: 'Translation failed' });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// 注入脚本的函数
|
||||
async function injectScriptsIfNeeded(tabId) {
|
||||
try {
|
||||
// 如果已经注入过,直接发送消息
|
||||
if (injectedTabs.has(tabId)) {
|
||||
await chrome.tabs.sendMessage(tabId, { action: 'startSelection' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 注入 CSS
|
||||
await chrome.scripting.insertCSS({
|
||||
target: { tabId },
|
||||
files: ['content.css']
|
||||
});
|
||||
|
||||
// 先注入 MD5
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['md5.js']
|
||||
});
|
||||
|
||||
// 再注入 content script
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['content.js']
|
||||
});
|
||||
|
||||
// 记录已注入状态
|
||||
injectedTabs.add(tabId);
|
||||
|
||||
// 等待一小段时间确保脚本加载完成
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// 发送消息
|
||||
await chrome.tabs.sendMessage(tabId, { action: 'startSelection' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Injection error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听标签页更新,清除注入状态
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||
if (changeInfo.status === 'loading') {
|
||||
injectedTabs.delete(tabId);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听标签页移除,清除注入状态
|
||||
chrome.tabs.onRemoved.addListener((tabId) => {
|
||||
injectedTabs.delete(tabId);
|
||||
});
|
||||
|
||||
// 添加 MD5 函数
|
||||
function md5(string) {
|
||||
function md5_RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||||
}
|
||||
|
||||
function md5_AddUnsigned(lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
}
|
||||
|
||||
function md5_F(x, y, z) { return (x & y) | ((~x) & z); }
|
||||
function md5_G(x, y, z) { return (x & z) | (y & (~z)); }
|
||||
function md5_H(x, y, z) { return (x ^ y ^ z); }
|
||||
function md5_I(x, y, z) { return (y ^ (x | (~z))); }
|
||||
|
||||
function md5_FF(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_GG(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_HH(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_II(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_ConvertToWordArray(string) {
|
||||
var lWordCount;
|
||||
var lMessageLength = string.length;
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8;
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
|
||||
var lWordArray = Array(lNumberOfWords - 1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
}
|
||||
|
||||
function md5_WordToHex(lValue) {
|
||||
var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
WordToHexValue_temp = "0" + lByte.toString(16);
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
|
||||
}
|
||||
return WordToHexValue;
|
||||
}
|
||||
|
||||
function md5_Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
var utftext = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return utftext;
|
||||
}
|
||||
|
||||
var x = Array();
|
||||
var k, AA, BB, CC, DD, a, b, c, d;
|
||||
var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
|
||||
var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
|
||||
var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
|
||||
var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
|
||||
|
||||
string = md5_Utf8Encode(string);
|
||||
x = md5_ConvertToWordArray(string);
|
||||
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
|
||||
|
||||
for (k = 0; k < x.length; k += 16) {
|
||||
AA = a; BB = b; CC = c; DD = d;
|
||||
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
|
||||
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
|
||||
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
|
||||
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
|
||||
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
|
||||
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
|
||||
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
|
||||
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
|
||||
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
|
||||
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
|
||||
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
|
||||
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
|
||||
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
|
||||
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
|
||||
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
|
||||
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
|
||||
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
|
||||
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
|
||||
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
|
||||
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
|
||||
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
|
||||
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||||
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
|
||||
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
|
||||
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
|
||||
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
|
||||
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
|
||||
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
|
||||
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
|
||||
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
|
||||
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
|
||||
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
|
||||
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
|
||||
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
|
||||
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
|
||||
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
|
||||
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
|
||||
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
|
||||
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
|
||||
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
|
||||
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
|
||||
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
|
||||
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
|
||||
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
|
||||
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
|
||||
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
|
||||
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
|
||||
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
|
||||
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
|
||||
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
|
||||
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
|
||||
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
|
||||
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
|
||||
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
|
||||
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
|
||||
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
|
||||
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
|
||||
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
|
||||
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
|
||||
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
|
||||
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
|
||||
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
|
||||
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
|
||||
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
|
||||
a = md5_AddUnsigned(a, AA);
|
||||
b = md5_AddUnsigned(b, BB);
|
||||
c = md5_AddUnsigned(c, CC);
|
||||
d = md5_AddUnsigned(d, DD);
|
||||
}
|
||||
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
|
||||
}
|
||||
|
||||
async function handleTranslation(text) {
|
||||
try {
|
||||
// 获取目标语言设置
|
||||
const { targetLang } = await chrome.storage.sync.get(['targetLang']);
|
||||
const to = targetLang || 'en';
|
||||
|
||||
const appid = '20241112002200806';
|
||||
const key = 'preM0becByYCdotRTP_a';
|
||||
const salt = Date.now().toString();
|
||||
const from = 'zh';
|
||||
|
||||
const str1 = appid + text + salt + key;
|
||||
const sign = md5(str1);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
q: text,
|
||||
from: from,
|
||||
to: to,
|
||||
appid: appid,
|
||||
salt: salt,
|
||||
sign: sign
|
||||
});
|
||||
|
||||
const url = 'https://api.fanyi.baidu.com/api/trans/vip/translate';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: params.toString()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Translation result:', result);
|
||||
|
||||
if (result.error_code) {
|
||||
throw new Error(result.error_msg || 'Translation failed');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Translation error:', error);
|
||||
return {
|
||||
error: true,
|
||||
message: error.message || 'Translation failed',
|
||||
trans_result: [{
|
||||
dst: 'Translation failed: ' + (error.message || 'Unknown error')
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 监听扩展安装或更新
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
console.log('Extension installed/updated');
|
||||
});
|
30
content.css
Normal file
30
content.css
Normal file
@ -0,0 +1,30 @@
|
||||
.selection-overlay {
|
||||
position: fixed;
|
||||
border: 2px dashed #4285f4;
|
||||
background: rgba(66, 133, 244, 0.1);
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.translation-popup {
|
||||
position: fixed;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
z-index: 10001;
|
||||
max-width: 300px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.page-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
z-index: 9999;
|
||||
cursor: crosshair;
|
||||
}
|
359
content.js
Normal file
359
content.js
Normal file
@ -0,0 +1,359 @@
|
||||
// 检查是否已经初始化
|
||||
if (window.translationToolInitialized) {
|
||||
throw new Error('Translation tool already initialized');
|
||||
}
|
||||
window.translationToolInitialized = true;
|
||||
|
||||
let isSelecting = false;
|
||||
let startX, startY;
|
||||
let selectionElement = null;
|
||||
let translationPopup = null;
|
||||
|
||||
// 监听来自background的消息
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === 'startSelection') {
|
||||
startSelectionMode();
|
||||
}
|
||||
});
|
||||
|
||||
function startSelectionMode() {
|
||||
if (isSelecting) return;
|
||||
|
||||
isSelecting = true;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'page-overlay';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
selectionElement = document.createElement('div');
|
||||
selectionElement.className = 'selection-overlay';
|
||||
document.body.appendChild(selectionElement);
|
||||
|
||||
document.addEventListener('mousedown', handleMouseDown);
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
function handleMouseDown(e) {
|
||||
if (!isSelecting) return;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
updateSelectionBox(e);
|
||||
}
|
||||
|
||||
function handleMouseMove(e) {
|
||||
if (!isSelecting || !startX) return;
|
||||
updateSelectionBox(e);
|
||||
}
|
||||
|
||||
function handleMouseUp(e) {
|
||||
if (!isSelecting) return;
|
||||
|
||||
const selectedText = getTextFromArea(
|
||||
Math.min(startX, e.clientX),
|
||||
Math.min(startY, e.clientY),
|
||||
Math.abs(e.clientX - startX),
|
||||
Math.abs(e.clientY - startY)
|
||||
);
|
||||
|
||||
if (selectedText.trim()) {
|
||||
translateText(selectedText.trim(), e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
function updateSelectionBox(e) {
|
||||
const left = Math.min(startX, e.clientX);
|
||||
const top = Math.min(startY, e.clientY);
|
||||
const width = Math.abs(e.clientX - startX);
|
||||
const height = Math.abs(e.clientY - startY);
|
||||
|
||||
selectionElement.style.left = left + 'px';
|
||||
selectionElement.style.top = top + 'px';
|
||||
selectionElement.style.width = width + 'px';
|
||||
selectionElement.style.height = height + 'px';
|
||||
}
|
||||
|
||||
function getTextFromArea(left, top, width, height) {
|
||||
const elements = document.elementsFromPoint(left + width/2, top + height/2);
|
||||
for (const element of elements) {
|
||||
if (element.textContent && element !== selectionElement) {
|
||||
return element.textContent;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async function translateText(text, x, y) {
|
||||
try {
|
||||
// 通过 chrome.runtime.sendMessage 发送翻译请求
|
||||
const result = await chrome.runtime.sendMessage({
|
||||
action: 'translate',
|
||||
text: text
|
||||
});
|
||||
|
||||
if (result && result.trans_result && result.trans_result[0]) {
|
||||
showTranslation(result.trans_result[0].dst, x, y);
|
||||
} else {
|
||||
showTranslation('Translation failed', x, y);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Translation failed:', error);
|
||||
showTranslation('Translation failed', x, y);
|
||||
}
|
||||
}
|
||||
|
||||
function showTranslation(translatedText, x, y) {
|
||||
if (translationPopup) {
|
||||
translationPopup.remove();
|
||||
}
|
||||
|
||||
translationPopup = document.createElement('div');
|
||||
translationPopup.className = 'translation-popup';
|
||||
translationPopup.textContent = translatedText;
|
||||
|
||||
translationPopup.style.left = x + 'px';
|
||||
translationPopup.style.top = (y + 20) + 'px';
|
||||
|
||||
document.body.appendChild(translationPopup);
|
||||
|
||||
// 点击其他地方关闭翻译框
|
||||
document.addEventListener('click', function closePopup(e) {
|
||||
if (e.target !== translationPopup) {
|
||||
translationPopup.remove();
|
||||
document.removeEventListener('click', closePopup);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
isSelecting = false;
|
||||
|
||||
const overlay = document.querySelector('.page-overlay');
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
|
||||
if (selectionElement) {
|
||||
selectionElement.remove();
|
||||
selectionElement = null;
|
||||
}
|
||||
|
||||
startX = startY = null;
|
||||
|
||||
document.removeEventListener('mousedown', handleMouseDown);
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
// 检测 Mac 和 Windows 的按键
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
const triggerKey = isMac ? e.metaKey : e.ctrlKey;
|
||||
|
||||
if (e.key === 'Escape' || (triggerKey && e.key === 'q')) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 MD5 函数
|
||||
function md5(string) {
|
||||
function md5_RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||||
}
|
||||
|
||||
function md5_AddUnsigned(lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
}
|
||||
|
||||
function md5_F(x, y, z) {
|
||||
return (x & y) | ((~x) & z);
|
||||
}
|
||||
|
||||
function md5_G(x, y, z) {
|
||||
return (x & z) | (y & (~z));
|
||||
}
|
||||
|
||||
function md5_H(x, y, z) {
|
||||
return (x ^ y ^ z);
|
||||
}
|
||||
|
||||
function md5_I(x, y, z) {
|
||||
return (y ^ (x | (~z)));
|
||||
}
|
||||
|
||||
function md5_FF(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_GG(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_HH(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_II(a, b, c, d, x, s, ac) {
|
||||
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
|
||||
return md5_AddUnsigned(md5_RotateLeft(a, s), b);
|
||||
}
|
||||
|
||||
function md5_ConvertToWordArray(string) {
|
||||
var lWordCount;
|
||||
var lMessageLength = string.length;
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8;
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
|
||||
var lWordArray = Array(lNumberOfWords - 1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
}
|
||||
|
||||
function md5_WordToHex(lValue) {
|
||||
var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
WordToHexValue_temp = "0" + lByte.toString(16);
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
|
||||
}
|
||||
return WordToHexValue;
|
||||
}
|
||||
|
||||
function md5_Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
var utftext = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return utftext;
|
||||
}
|
||||
|
||||
var x = Array();
|
||||
var k, AA, BB, CC, DD, a, b, c, d;
|
||||
var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
|
||||
var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
|
||||
var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
|
||||
var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
|
||||
string = md5_Utf8Encode(string);
|
||||
x = md5_ConvertToWordArray(string);
|
||||
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
|
||||
for (k = 0; k < x.length; k += 16) {
|
||||
AA = a; BB = b; CC = c; DD = d;
|
||||
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
|
||||
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
|
||||
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
|
||||
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
|
||||
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
|
||||
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
|
||||
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
|
||||
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
|
||||
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
|
||||
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
|
||||
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
|
||||
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
|
||||
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
|
||||
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
|
||||
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
|
||||
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
|
||||
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
|
||||
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
|
||||
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
|
||||
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
|
||||
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
|
||||
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||||
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
|
||||
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
|
||||
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
|
||||
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
|
||||
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
|
||||
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
|
||||
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
|
||||
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
|
||||
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
|
||||
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
|
||||
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
|
||||
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
|
||||
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
|
||||
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
|
||||
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
|
||||
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
|
||||
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
|
||||
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
|
||||
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
|
||||
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
|
||||
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
|
||||
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
|
||||
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
|
||||
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
|
||||
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
|
||||
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
|
||||
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
|
||||
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
|
||||
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
|
||||
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
|
||||
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
|
||||
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
|
||||
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
|
||||
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
|
||||
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
|
||||
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
|
||||
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
|
||||
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
|
||||
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
|
||||
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
|
||||
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
|
||||
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
|
||||
a = md5_AddUnsigned(a, AA);
|
||||
b = md5_AddUnsigned(b, BB);
|
||||
c = md5_AddUnsigned(c, CC);
|
||||
d = md5_AddUnsigned(d, DD);
|
||||
}
|
||||
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
|
||||
}
|
@ -16,8 +16,33 @@
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
},
|
||||
"permissions": ["activeTab", "downloads"],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'"
|
||||
}
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
"downloads",
|
||||
"scripting",
|
||||
"storage",
|
||||
"tabs"
|
||||
],
|
||||
"host_permissions": [
|
||||
"https://api.fanyi.baidu.com/*",
|
||||
"<all_urls>"
|
||||
],
|
||||
"commands": {
|
||||
"trigger-select": {
|
||||
"suggested_key": {
|
||||
"default": "Ctrl+Q",
|
||||
"mac": "Command+Q"
|
||||
},
|
||||
"description": "触发框选功能"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"content_scripts": [{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["md5.js", "content.js"],
|
||||
"css": ["content.css"]
|
||||
}]
|
||||
}
|
19
md5.js
Normal file
19
md5.js
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
var CryptoJS=CryptoJS||function(s,p){var m={},l=m.lib={},n=function(){},r=l.Base={extend:function(b){n.prototype=this;var h=new n;b&&h.mixIn(b);h.hasOwnProperty("init")||(h.init=function(){h.$super.init.apply(this,arguments)});h.init.prototype=h;h.$super=this;return h},create:function(){var b=this.extend();b.init.apply(b,arguments);return b},init:function(){},mixIn:function(b){for(var h in b)b.hasOwnProperty(h)&&(this[h]=b[h]);b.hasOwnProperty("toString")&&(this.toString=b.toString)},clone:function(){return this.init.prototype.extend(this)}},
|
||||
q=l.WordArray=r.extend({init:function(b,h){b=this.words=b||[];this.sigBytes=h!=p?h:4*b.length},toString:function(b){return(b||t).stringify(this)},concat:function(b){var h=this.words,a=b.words,j=this.sigBytes;b=b.sigBytes;this.clamp();if(j%4)for(var g=0;g<b;g++)h[j+g>>>2]|=(a[g>>>2]>>>24-8*(g%4)&255)<<24-8*((j+g)%4);else if(65535<a.length)for(g=0;g<b;g+=4)h[j+g>>>2]=a[g>>>2];else h.push.apply(h,a);this.sigBytes+=b;return this},clamp:function(){var b=this.words,h=this.sigBytes;b[h>>>2]&=4294967295<<
|
||||
32-8*(h%4);b.length=s.ceil(h/4)},clone:function(){var b=r.clone.call(this);b.words=this.words.slice(0);return b},random:function(b){for(var h=[],a=0;a<b;a+=4)h.push(4294967296*s.random()|0);return new q.init(h,b)}}),v=m.enc={},t=v.Hex={stringify:function(b){var a=b.words;b=b.sigBytes;for(var g=[],j=0;j<b;j++){var k=a[j>>>2]>>>24-8*(j%4)&255;g.push((k>>>4).toString(16));g.push((k&15).toString(16))}return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j<a;j+=2)g[j>>>3]|=parseInt(b.substr(j,
|
||||
2),16)<<24-4*(j%8);return new q.init(g,a/2)}},a=v.Latin1={stringify:function(b){var a=b.words;b=b.sigBytes;for(var g=[],j=0;j<b;j++)g.push(String.fromCharCode(a[j>>>2]>>>24-8*(j%4)&255));return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j<a;j++)g[j>>>2]|=(b.charCodeAt(j)&255)<<24-8*(j%4);return new q.init(g,a)}},u=v.Utf8={stringify:function(b){try{return decodeURIComponent(escape(a.stringify(b)))}catch(g){throw Error("Malformed UTF-8 data");}},parse:function(b){return a.parse(unescape(encodeURIComponent(b)))}},
|
||||
g=l.BufferedBlockAlgorithm=r.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(b){"string"==typeof b&&(b=u.parse(b));this._data.concat(b);this._nDataBytes+=b.sigBytes},_process:function(b){var a=this._data,g=a.words,j=a.sigBytes,k=this.blockSize,m=j/(4*k),m=b?s.ceil(m):s.max((m|0)-this._minBufferSize,0);b=m*k;j=s.min(4*b,j);if(b){for(var l=0;l<b;l+=k)this._doProcessBlock(g,l);l=g.splice(0,b);a.sigBytes-=j}return new q.init(l,j)},clone:function(){var b=r.clone.call(this);
|
||||
b._data=this._data.clone();return b},_minBufferSize:0});l.Hasher=g.extend({cfg:r.extend(),init:function(b){this.cfg=this.cfg.extend(b);this.reset()},reset:function(){g.reset.call(this);this._doReset()},update:function(b){this._append(b);this._process();return this},finalize:function(b){b&&this._append(b);return this._doFinalize()},blockSize:16,_createHelper:function(b){return function(a,g){return(new b.init(g)).finalize(a)}},_createHmacHelper:function(b){return function(a,g){return(new k.HMAC.init(b,
|
||||
g)).finalize(a)}}});var k=m.algo={};return m}(Math);
|
||||
(function(s){function p(a,k,b,h,l,j,m){a=a+(k&b|~k&h)+l+m;return(a<<j|a>>>32-j)+k}function m(a,k,b,h,l,j,m){a=a+(k&h|b&~h)+l+m;return(a<<j|a>>>32-j)+k}function l(a,k,b,h,l,j,m){a=a+(k^b^h)+l+m;return(a<<j|a>>>32-j)+k}function n(a,k,b,h,l,j,m){a=a+(b^(k|~h))+l+m;return(a<<j|a>>>32-j)+k}for(var r=CryptoJS,q=r.lib,v=q.WordArray,t=q.Hasher,q=r.algo,a=[],u=0;64>u;u++)a[u]=4294967296*s.abs(s.sin(u+1))|0;q=q.MD5=t.extend({_doReset:function(){this._hash=new v.init([1732584193,4023233417,2562383102,271733878])},
|
||||
_doProcessBlock:function(g,k){for(var b=0;16>b;b++){var h=k+b,w=g[h];g[h]=(w<<8|w>>>24)&16711935|(w<<24|w>>>8)&4278255360}var b=this._hash.words,h=g[k+0],w=g[k+1],j=g[k+2],q=g[k+3],r=g[k+4],s=g[k+5],t=g[k+6],u=g[k+7],v=g[k+8],x=g[k+9],y=g[k+10],z=g[k+11],A=g[k+12],B=g[k+13],C=g[k+14],D=g[k+15],c=b[0],d=b[1],e=b[2],f=b[3],c=p(c,d,e,f,h,7,a[0]),f=p(f,c,d,e,w,12,a[1]),e=p(e,f,c,d,j,17,a[2]),d=p(d,e,f,c,q,22,a[3]),c=p(c,d,e,f,r,7,a[4]),f=p(f,c,d,e,s,12,a[5]),e=p(e,f,c,d,t,17,a[6]),d=p(d,e,f,c,u,22,a[7]),
|
||||
c=p(c,d,e,f,v,7,a[8]),f=p(f,c,d,e,x,12,a[9]),e=p(e,f,c,d,y,17,a[10]),d=p(d,e,f,c,z,22,a[11]),c=p(c,d,e,f,A,7,a[12]),f=p(f,c,d,e,B,12,a[13]),e=p(e,f,c,d,C,17,a[14]),d=p(d,e,f,c,D,22,a[15]),c=m(c,d,e,f,w,5,a[16]),f=m(f,c,d,e,t,9,a[17]),e=m(e,f,c,d,z,14,a[18]),d=m(d,e,f,c,h,20,a[19]),c=m(c,d,e,f,s,5,a[20]),f=m(f,c,d,e,y,9,a[21]),e=m(e,f,c,d,D,14,a[22]),d=m(d,e,f,c,r,20,a[23]),c=m(c,d,e,f,x,5,a[24]),f=m(f,c,d,e,C,9,a[25]),e=m(e,f,c,d,q,14,a[26]),d=m(d,e,f,c,v,20,a[27]),c=m(c,d,e,f,B,5,a[28]),f=m(f,c,
|
||||
d,e,j,9,a[29]),e=m(e,f,c,d,u,14,a[30]),d=m(d,e,f,c,A,20,a[31]),c=l(c,d,e,f,s,4,a[32]),f=l(f,c,d,e,v,11,a[33]),e=l(e,f,c,d,z,16,a[34]),d=l(d,e,f,c,C,23,a[35]),c=l(c,d,e,f,w,4,a[36]),f=l(f,c,d,e,r,11,a[37]),e=l(e,f,c,d,u,16,a[38]),d=l(d,e,f,c,y,23,a[39]),c=l(c,d,e,f,B,4,a[40]),f=l(f,c,d,e,h,11,a[41]),e=l(e,f,c,d,q,16,a[42]),d=l(d,e,f,c,t,23,a[43]),c=l(c,d,e,f,x,4,a[44]),f=l(f,c,d,e,A,11,a[45]),e=l(e,f,c,d,D,16,a[46]),d=l(d,e,f,c,j,23,a[47]),c=n(c,d,e,f,h,6,a[48]),f=n(f,c,d,e,u,10,a[49]),e=n(e,f,c,d,
|
||||
C,15,a[50]),d=n(d,e,f,c,s,21,a[51]),c=n(c,d,e,f,A,6,a[52]),f=n(f,c,d,e,q,10,a[53]),e=n(e,f,c,d,y,15,a[54]),d=n(d,e,f,c,w,21,a[55]),c=n(c,d,e,f,v,6,a[56]),f=n(f,c,d,e,D,10,a[57]),e=n(e,f,c,d,t,15,a[58]),d=n(d,e,f,c,B,21,a[59]),c=n(c,d,e,f,r,6,a[60]),f=n(f,c,d,e,z,10,a[61]),e=n(e,f,c,d,j,15,a[62]),d=n(d,e,f,c,x,21,a[63]);b[0]=b[0]+c|0;b[1]=b[1]+d|0;b[2]=b[2]+e|0;b[3]=b[3]+f|0},_doFinalize:function(){var a=this._data,k=a.words,b=8*this._nDataBytes,h=8*a.sigBytes;k[h>>>5]|=128<<24-h%32;var l=s.floor(b/
|
||||
4294967296);k[(h+64>>>9<<4)+15]=(l<<8|l>>>24)&16711935|(l<<24|l>>>8)&4278255360;k[(h+64>>>9<<4)+14]=(b<<8|b>>>24)&16711935|(b<<24|b>>>8)&4278255360;a.sigBytes=4*(k.length+1);this._process();a=this._hash;k=a.words;for(b=0;4>b;b++)h=k[b],k[b]=(h<<8|h>>>24)&16711935|(h<<24|h>>>8)&4278255360;return a},clone:function(){var a=t.clone.call(this);a._hash=this._hash.clone();return a}});r.MD5=t._createHelper(q);r.HmacMD5=t._createHmacHelper(q)})(Math);
|
23
popup.html
23
popup.html
@ -18,6 +18,10 @@
|
||||
<span class="icon">📄</span>
|
||||
Word转HTML
|
||||
</button>
|
||||
<button class="tablinks" data-tab="translate">
|
||||
<span class="icon">🔤</span>
|
||||
翻译设置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="qr" class="tabcontent" style="display:block;">
|
||||
@ -43,6 +47,25 @@
|
||||
下载HTML
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="translate" class="tabcontent" style="display:none;">
|
||||
<div class="translate-settings">
|
||||
<div class="setting-item">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="translateEnabled">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<span class="setting-label">启用翻译功能 (Ctrl+Q)</span>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>目标语言:</label>
|
||||
<select id="targetLang">
|
||||
<option value="en">英文</option>
|
||||
<option value="cht">繁体中文</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
|
42
popup.js
42
popup.js
@ -10,12 +10,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
let qr = null;
|
||||
let convertedHtml = '';
|
||||
|
||||
// 加载翻译设置
|
||||
loadTranslationSettings();
|
||||
|
||||
const tabs = document.querySelectorAll('.tablinks');
|
||||
const tabContents = document.querySelectorAll('.tabcontent');
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', (e) => {
|
||||
const tabName = e.target.getAttribute('data-tab');
|
||||
const tabName = e.currentTarget.getAttribute('data-tab');
|
||||
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
@ -131,4 +134,41 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
saveAs: true
|
||||
});
|
||||
});
|
||||
|
||||
// 翻译设置相关
|
||||
function loadTranslationSettings() {
|
||||
const translateEnabled = document.getElementById('translateEnabled');
|
||||
const targetLang = document.getElementById('targetLang');
|
||||
|
||||
// 检测操作系统
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
const shortcutText = isMac ? 'Command+Q' : 'Ctrl+Q';
|
||||
|
||||
// 更新显示的快捷键文本
|
||||
const settingLabel = document.querySelector('.setting-label');
|
||||
if (settingLabel) {
|
||||
settingLabel.textContent = `启用翻译功能 (${shortcutText})`;
|
||||
}
|
||||
|
||||
// 加载保存的设置
|
||||
chrome.storage.sync.get(['translateEnabled', 'targetLang'], function(result) {
|
||||
translateEnabled.checked = result.translateEnabled !== false; // 默认为 true
|
||||
targetLang.value = result.targetLang || 'en';
|
||||
});
|
||||
|
||||
// 监听设置变更
|
||||
translateEnabled.addEventListener('change', function(e) {
|
||||
const enabled = e.target.checked;
|
||||
chrome.storage.sync.set({ translateEnabled: enabled }, function() {
|
||||
console.log('Translation enabled:', enabled);
|
||||
});
|
||||
});
|
||||
|
||||
targetLang.addEventListener('change', function(e) {
|
||||
const lang = e.target.value;
|
||||
chrome.storage.sync.set({ targetLang: lang }, function() {
|
||||
console.log('Target language:', lang);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
85
styles.css
85
styles.css
@ -10,7 +10,7 @@ body {
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 380px;
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
@ -26,27 +26,30 @@ h1 {
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
background: #f5f5f5;
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
min-height: 45px;
|
||||
}
|
||||
|
||||
.tab button {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
padding: 8px 4px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.tab button:hover {
|
||||
@ -148,7 +151,8 @@ button:hover {
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 美化滚动条 */
|
||||
@ -241,4 +245,73 @@ button:hover {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 翻译设置样式 */
|
||||
.translate-settings {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* 开关按钮样式 */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
/* 下拉菜单样式 */
|
||||
select {
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
margin-left: 10px;
|
||||
}
|
Loading…
Reference in New Issue
Block a user