Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0bb1ddbf88 |
120
README.md
120
README.md
@ -1,89 +1,85 @@
|
|||||||
# 丰链工具箱 Chrome 扩展
|
# 丰链工具箱 Chrome 扩展
|
||||||
|
|
||||||
一个集成多种实用工具的 Chrome 扩展,包括二维码生成、Word 转 HTML、Word 文档翻译等功能。
|
一个实用的 Chrome 扩展工具集合,集成了二维码生成、Word 转 HTML、中英文翻译等实用功能。
|
||||||
|
|
||||||
## 功能特点
|
## 功能特性
|
||||||
|
|
||||||
### 1. 二维码生成
|
### 二维码生成器 🔲
|
||||||
- 自动获取当前标签页 URL 并生成二维码
|
- 自动获取当前标签页 URL 并生成二维码
|
||||||
- 支持手动输入文本或链接生成二维码
|
- 支持手动输入文本/链接生成二维码
|
||||||
- 高清晰度二维码输出
|
- 生成高清晰度二维码,支持长文本
|
||||||
|
- 即时预览生成结果
|
||||||
|
|
||||||
### 2. Word 转 HTML
|
### Word 转 HTML 转换器 📄
|
||||||
- 支持 .docx 文件转换为 HTML
|
- 支持 .docx 文件转换为 HTML
|
||||||
- 保持原文档格式和样式
|
- 保留原文档格式和样式
|
||||||
- 实时预览转换结果
|
- 实时预览转换结果
|
||||||
- 一键下载 HTML 文件
|
- 支持一键下载转换后的 HTML 文件
|
||||||
|
- 自动处理图片和表格
|
||||||
|
|
||||||
### 3. Word 文档翻译
|
### 中英文翻译 🔤
|
||||||
- 支持中文文档翻译为英文、日文、繁体中文
|
- 支持 Windows(Ctrl+Q) 和 Mac(Command+Q) 快捷键激活
|
||||||
- 完整保留原文档格式(包括字体、颜色、加粗等样式)
|
- 框选任意区域中文文本进行翻译
|
||||||
- 支持表格内容翻译
|
- 支持中英互译和繁体转换
|
||||||
- 实时显示翻译进度
|
- 翻译结果即时显示在选区旁边
|
||||||
- 保持文档的换行格式
|
- 点击任意位置关闭翻译结果
|
||||||
- 自动重试机制,提高翻译稳定性
|
|
||||||
|
|
||||||
## 安装说明
|
## 系统要求
|
||||||
|
|
||||||
### 扩展安装
|
- Chrome 88+ 或其他基于 Chromium 的浏览器
|
||||||
|
- Windows/MacOS/Linux 系统均可运行
|
||||||
|
|
||||||
|
## 安装方法
|
||||||
|
|
||||||
|
### 从 Chrome 商店安装
|
||||||
|
1. 访问 Chrome 网上应用店
|
||||||
|
2. 搜索"丰链工具箱"
|
||||||
|
3. 点击"添加至 Chrome"
|
||||||
|
|
||||||
|
### 开发者模式安装
|
||||||
1. 下载本项目代码
|
1. 下载本项目代码
|
||||||
2. 打开 Chrome 浏览器,进入扩展管理页面 (chrome://extensions/)
|
2. 打开 Chrome 浏览器,进入扩展程序页面 (chrome://extensions/)
|
||||||
3. 开启"开发者模式"
|
3. 开启右上角的"开发者模式"
|
||||||
4. 点击"加载已解压的扩展程序",选择项目目录
|
4. 点击"加载已解压的扩展程序"
|
||||||
|
5. 选择项目文件夹即可完成安装
|
||||||
|
|
||||||
### 后端服务安装
|
## 使用方法
|
||||||
1. 确保已安装 Python 3.8+
|
|
||||||
2. 进入 backend 目录
|
|
||||||
3. 创建虚拟环境(可选):
|
|
||||||
```bash
|
|
||||||
python -m venv venv
|
|
||||||
# Windows
|
|
||||||
venv\Scripts\activate
|
|
||||||
# Mac/Linux
|
|
||||||
source venv/bin/activate
|
|
||||||
```
|
|
||||||
4. 安装依赖:
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
5. 启动后端服务:
|
|
||||||
```bash
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用说明
|
|
||||||
|
|
||||||
### 二维码生成
|
### 二维码生成
|
||||||
1. 点击扩展图标打开工具箱
|
1. 点击扩展图标打开工具箱
|
||||||
2. 默认显示当前页面 URL 的二维码
|
2. 默认显示当前页面的二维码
|
||||||
3. 可在输入框中修改文本,点击"生成二维码"更新
|
3. 可在输入框中输入任意文本/链接
|
||||||
|
4. 点击"生成二维码"按钮即可
|
||||||
|
|
||||||
### Word 转 HTML
|
### Word 转 HTML
|
||||||
1. 选择"Word转HTML"选项卡
|
1. 点击"Word转HTML"标签页
|
||||||
2. 点击上传区域选择 .docx 文件
|
2. 点击选择或拖拽上传 Word 文档
|
||||||
3. 等待转换完成后预览结果
|
3. 等待转换完成后可预览效果
|
||||||
4. 点击"下载HTML"保存文件
|
4. 点击"下载HTML"保存文件
|
||||||
|
|
||||||
### Word 文档翻译
|
### 翻译功能
|
||||||
1. 选择"Word翻译"选项卡
|
1. 点击"翻译设置"标签页开启功能
|
||||||
2. 点击上传区域选择要翻译的 Word 文档
|
2. 选择目标语言(英文/繁体中文)
|
||||||
3. 从下拉菜单选择目标语言(英文/日文/繁体中文)
|
3. 在任意页面按下快捷键(Ctrl+Q/Command+Q)
|
||||||
4. 点击"开始翻译"按钮
|
4. 框选需要翻译的文本
|
||||||
5. 等待翻译完成,自动下载翻译后的文档
|
5. 翻译结果会显示在选区旁边
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. 使用翻译功能时需要确保后端服务正在运行
|
|
||||||
2. 翻译功能使用百度翻译 API,请确保网络连接正常
|
|
||||||
3. 大文件翻译可能需要较长时间,请耐心等待
|
|
||||||
4. 建议定期清理 temp 目录下的临时文件
|
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
- 前端:HTML, CSS, JavaScript
|
- HTML5/CSS3
|
||||||
- 后端:Python, Flask
|
- JavaScript ES6+
|
||||||
- 依赖库:python-docx, mammoth.js, qrcode.js
|
- Chrome Extension API
|
||||||
|
- QRCode.js - 二维码生成
|
||||||
|
- Mammoth.js - Word 文档转换
|
||||||
|
- 百度翻译 API - 文本翻译
|
||||||
|
|
||||||
## 开发者
|
## 开发者
|
||||||
|
|
||||||
Made by Scout
|
Made by Scout
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.0.0
|
||||||
|
- 初始版本发布
|
||||||
|
- 实现基础功能:二维码生成、Word转HTML、翻译
|
||||||
|
- 支持 Windows/Mac 快捷键
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
from flask import Flask, request, send_file
|
|
||||||
from flask_cors import CORS
|
|
||||||
import os
|
|
||||||
from utils.docx_translator import DocxTranslator
|
|
||||||
import shutil
|
|
||||||
import time
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
CORS(app, resources={
|
|
||||||
r"/*": {
|
|
||||||
"origins": ["chrome-extension://*"],
|
|
||||||
"methods": ["POST", "OPTIONS", "GET"],
|
|
||||||
"allow_headers": ["Content-Type"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 全局变量存储翻译进度
|
|
||||||
translation_progress = 0
|
|
||||||
|
|
||||||
UPLOAD_FOLDER = 'temp'
|
|
||||||
if not os.path.exists(UPLOAD_FOLDER):
|
|
||||||
os.makedirs(UPLOAD_FOLDER)
|
|
||||||
|
|
||||||
def update_progress(progress):
|
|
||||||
global translation_progress
|
|
||||||
translation_progress = progress
|
|
||||||
|
|
||||||
@app.route('/progress', methods=['GET'])
|
|
||||||
def get_progress():
|
|
||||||
return {'progress': translation_progress}
|
|
||||||
|
|
||||||
@app.route('/translate', methods=['POST'])
|
|
||||||
def translate_docx():
|
|
||||||
global translation_progress
|
|
||||||
translation_progress = 0
|
|
||||||
input_path = None
|
|
||||||
output_path = None
|
|
||||||
|
|
||||||
if 'file' not in request.files:
|
|
||||||
return {'error': 'No file provided'}, 400
|
|
||||||
|
|
||||||
file = request.files['file']
|
|
||||||
target_lang = request.form.get('target_lang', 'en')
|
|
||||||
|
|
||||||
if file.filename == '':
|
|
||||||
return {'error': 'No file selected'}, 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
timestamp = str(int(time.time()))
|
|
||||||
safe_filename = f"{timestamp}_{file.filename}"
|
|
||||||
input_path = os.path.join(UPLOAD_FOLDER, safe_filename)
|
|
||||||
file.save(input_path)
|
|
||||||
|
|
||||||
translator = DocxTranslator()
|
|
||||||
output_path = translator.translate_document(
|
|
||||||
input_path,
|
|
||||||
target_lang=target_lang,
|
|
||||||
progress_callback=update_progress
|
|
||||||
)
|
|
||||||
|
|
||||||
temp_output = output_path + '.tmp'
|
|
||||||
shutil.copy2(output_path, temp_output)
|
|
||||||
|
|
||||||
return send_file(
|
|
||||||
temp_output,
|
|
||||||
as_attachment=True,
|
|
||||||
download_name=f"translated_{file.filename}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return {'error': str(e)}, 500
|
|
||||||
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
if input_path and os.path.exists(input_path):
|
|
||||||
os.remove(input_path)
|
|
||||||
if output_path and os.path.exists(output_path):
|
|
||||||
os.remove(output_path)
|
|
||||||
if 'temp_output' in locals() and os.path.exists(temp_output):
|
|
||||||
os.remove(temp_output)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error cleaning up files: {e}")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(debug=True, port=5000)
|
|
@ -1,5 +0,0 @@
|
|||||||
flask==2.3.3
|
|
||||||
werkzeug==2.3.7
|
|
||||||
flask-cors==4.0.0
|
|
||||||
python-docx==0.8.11
|
|
||||||
requests==2.31.0
|
|
Binary file not shown.
@ -1,244 +0,0 @@
|
|||||||
from docx import Document
|
|
||||||
import hashlib
|
|
||||||
import time
|
|
||||||
import requests
|
|
||||||
import os
|
|
||||||
|
|
||||||
class DocxTranslator:
|
|
||||||
def __init__(self):
|
|
||||||
self.appid = "20241112002200806"
|
|
||||||
self.secret_key = "preM0becByYCdotRTP_a"
|
|
||||||
self.api_url = "https://api.fanyi.baidu.com/api/trans/vip/translate"
|
|
||||||
self.max_length = 2000
|
|
||||||
self.lang_map = {
|
|
||||||
'en': 'en', # English
|
|
||||||
'jp': 'jp', # Japanese
|
|
||||||
'cht': 'cht' # Traditional Chinese
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_sign(self, text, salt):
|
|
||||||
sign_str = f"{self.appid}{text}{salt}{self.secret_key}"
|
|
||||||
return hashlib.md5(sign_str.encode()).hexdigest()
|
|
||||||
|
|
||||||
def translate_text(self, text, to_lang):
|
|
||||||
target_lang = self.lang_map.get(to_lang, to_lang)
|
|
||||||
|
|
||||||
if len(text) > self.max_length:
|
|
||||||
segments = text.split('。')
|
|
||||||
current_segment = ''
|
|
||||||
translated_segments = []
|
|
||||||
|
|
||||||
for segment in segments:
|
|
||||||
if len(current_segment) + len(segment) < self.max_length:
|
|
||||||
current_segment += segment + '。'
|
|
||||||
else:
|
|
||||||
if current_segment:
|
|
||||||
translated_segments.append(self._translate_segment(current_segment, target_lang))
|
|
||||||
current_segment = segment + '。'
|
|
||||||
|
|
||||||
if current_segment:
|
|
||||||
translated_segments.append(self._translate_segment(current_segment, target_lang))
|
|
||||||
|
|
||||||
return ''.join(translated_segments)
|
|
||||||
else:
|
|
||||||
return self._translate_segment(text, target_lang)
|
|
||||||
|
|
||||||
def _translate_segment(self, text, to_lang):
|
|
||||||
if not text.strip():
|
|
||||||
return text
|
|
||||||
|
|
||||||
salt = str(int(time.time()))
|
|
||||||
sign = self._get_sign(text, salt)
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'q': text,
|
|
||||||
'from': 'zh',
|
|
||||||
'to': to_lang,
|
|
||||||
'appid': self.appid,
|
|
||||||
'salt': salt,
|
|
||||||
'sign': sign
|
|
||||||
}
|
|
||||||
|
|
||||||
max_retries = 3
|
|
||||||
for attempt in range(max_retries):
|
|
||||||
try:
|
|
||||||
response = requests.get(self.api_url, params=params)
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if 'error_code' in result:
|
|
||||||
if attempt < max_retries - 1:
|
|
||||||
time.sleep(1)
|
|
||||||
continue
|
|
||||||
raise Exception(f"Translation error: {result['error_msg']}")
|
|
||||||
|
|
||||||
return result['trans_result'][0]['dst']
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if attempt < max_retries - 1:
|
|
||||||
time.sleep(1)
|
|
||||||
continue
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def translate_document(self, input_path, target_lang='en', progress_callback=None):
|
|
||||||
doc = Document(input_path)
|
|
||||||
output_path = input_path.replace('.docx', f'_translated.docx')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 计算总翻译项
|
|
||||||
total_items = len(doc.paragraphs)
|
|
||||||
for table in doc.tables:
|
|
||||||
total_items += sum(len(row.cells) for row in table.rows)
|
|
||||||
|
|
||||||
current_item = 0
|
|
||||||
|
|
||||||
# 翻译段落
|
|
||||||
for paragraph in doc.paragraphs:
|
|
||||||
if paragraph.text.strip():
|
|
||||||
try:
|
|
||||||
# 保存原始的段落格式和换行
|
|
||||||
runs = paragraph.runs
|
|
||||||
original_runs = []
|
|
||||||
|
|
||||||
# 收集每个run的文本和格式信息,保留原始换行符
|
|
||||||
for run in runs:
|
|
||||||
text = run.text
|
|
||||||
if text: # 不去除空白字符,保留原始格式
|
|
||||||
original_runs.append({
|
|
||||||
'text': text,
|
|
||||||
'bold': run.bold,
|
|
||||||
'italic': run.italic,
|
|
||||||
'underline': run.underline,
|
|
||||||
'font_name': run.font.name,
|
|
||||||
'font_size': run.font.size,
|
|
||||||
'color_rgb': run.font.color.rgb if run.font.color else None,
|
|
||||||
'break_type': 'break' if any(text.endswith(x) for x in ['\n', '\v', '\r']) else None
|
|
||||||
})
|
|
||||||
|
|
||||||
# 清除原有内容但保持段落格式
|
|
||||||
paragraph.clear()
|
|
||||||
|
|
||||||
# 分别翻译和添加每个run的文本
|
|
||||||
for i, orig_run in enumerate(original_runs):
|
|
||||||
if orig_run['text']:
|
|
||||||
# 保留换行符
|
|
||||||
has_break = orig_run['break_type'] == 'break'
|
|
||||||
text_to_translate = orig_run['text'].rstrip('\n\r\v')
|
|
||||||
|
|
||||||
# 翻译非空文本
|
|
||||||
if text_to_translate.strip():
|
|
||||||
translated_text = self.translate_text(text_to_translate, target_lang)
|
|
||||||
else:
|
|
||||||
translated_text = text_to_translate
|
|
||||||
|
|
||||||
# 创建新的run并应用格式
|
|
||||||
new_run = paragraph.add_run()
|
|
||||||
new_run.bold = orig_run['bold']
|
|
||||||
new_run.italic = orig_run['italic']
|
|
||||||
new_run.underline = orig_run['underline']
|
|
||||||
new_run.font.name = orig_run['font_name']
|
|
||||||
if orig_run['font_size']:
|
|
||||||
new_run.font.size = orig_run['font_size']
|
|
||||||
if orig_run['color_rgb']:
|
|
||||||
new_run.font.color.rgb = orig_run['color_rgb']
|
|
||||||
|
|
||||||
# 添加翻译后的文本
|
|
||||||
new_run.text = translated_text
|
|
||||||
|
|
||||||
# 如果原文有换行,添加换行符
|
|
||||||
if has_break:
|
|
||||||
new_run.add_break() # 不指定类型,使用默认换行
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error translating paragraph: {e}")
|
|
||||||
# 保留原文和格式
|
|
||||||
paragraph.clear()
|
|
||||||
for run in runs:
|
|
||||||
new_run = paragraph.add_run(run.text)
|
|
||||||
new_run.bold = run.bold
|
|
||||||
new_run.italic = run.italic
|
|
||||||
new_run.underline = run.underline
|
|
||||||
new_run.font.name = run.font.name
|
|
||||||
if run.font.size:
|
|
||||||
new_run.font.size = run.font.size
|
|
||||||
if run.font.color and run.font.color.rgb:
|
|
||||||
new_run.font.color.rgb = run.font.color.rgb
|
|
||||||
|
|
||||||
current_item += 1
|
|
||||||
if progress_callback:
|
|
||||||
progress_callback(int((current_item / total_items) * 100))
|
|
||||||
|
|
||||||
# 翻译表格 (使用相同的逻辑处理表格单元格)
|
|
||||||
for table in doc.tables:
|
|
||||||
for row in table.rows:
|
|
||||||
for cell in row.cells:
|
|
||||||
if cell.text.strip():
|
|
||||||
try:
|
|
||||||
for paragraph in cell.paragraphs:
|
|
||||||
runs = paragraph.runs
|
|
||||||
original_runs = []
|
|
||||||
|
|
||||||
for run in runs:
|
|
||||||
text = run.text
|
|
||||||
if text:
|
|
||||||
original_runs.append({
|
|
||||||
'text': text,
|
|
||||||
'bold': run.bold,
|
|
||||||
'italic': run.italic,
|
|
||||||
'underline': run.underline,
|
|
||||||
'font_name': run.font.name,
|
|
||||||
'font_size': run.font.size,
|
|
||||||
'color_rgb': run.font.color.rgb if run.font.color else None,
|
|
||||||
'break_type': 'break' if any(text.endswith(x) for x in ['\n', '\v', '\r']) else None
|
|
||||||
})
|
|
||||||
|
|
||||||
paragraph.clear()
|
|
||||||
|
|
||||||
for i, orig_run in enumerate(original_runs):
|
|
||||||
if orig_run['text']:
|
|
||||||
has_break = orig_run['break_type'] == 'break'
|
|
||||||
text_to_translate = orig_run['text'].rstrip('\n\r\v')
|
|
||||||
|
|
||||||
if text_to_translate.strip():
|
|
||||||
translated_text = self.translate_text(text_to_translate, target_lang)
|
|
||||||
else:
|
|
||||||
translated_text = text_to_translate
|
|
||||||
|
|
||||||
new_run = paragraph.add_run()
|
|
||||||
new_run.bold = orig_run['bold']
|
|
||||||
new_run.italic = orig_run['italic']
|
|
||||||
new_run.underline = orig_run['underline']
|
|
||||||
new_run.font.name = orig_run['font_name']
|
|
||||||
if orig_run['font_size']:
|
|
||||||
new_run.font.size = orig_run['font_size']
|
|
||||||
if orig_run['color_rgb']:
|
|
||||||
new_run.font.color.rgb = orig_run['color_rgb']
|
|
||||||
|
|
||||||
new_run.text = translated_text
|
|
||||||
|
|
||||||
if has_break:
|
|
||||||
new_run.add_break()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error translating cell: {e}")
|
|
||||||
# 保留原文和格式
|
|
||||||
for paragraph in cell.paragraphs:
|
|
||||||
paragraph.clear()
|
|
||||||
for run in runs:
|
|
||||||
new_run = paragraph.add_run(run.text)
|
|
||||||
new_run.bold = run.bold
|
|
||||||
new_run.italic = run.italic
|
|
||||||
new_run.underline = run.underline
|
|
||||||
new_run.font.name = run.font.name
|
|
||||||
if run.font.size:
|
|
||||||
new_run.font.size = run.font.size
|
|
||||||
if run.font.color and run.font.color.rgb:
|
|
||||||
new_run.font.color.rgb = run.font.color.rgb
|
|
||||||
|
|
||||||
current_item += 1
|
|
||||||
if progress_callback:
|
|
||||||
progress_callback(int((current_item / total_items) * 100))
|
|
||||||
|
|
||||||
doc.save(output_path)
|
|
||||||
return output_path
|
|
||||||
finally:
|
|
||||||
del doc
|
|
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();
|
||||||
|
}
|
@ -9,12 +9,40 @@
|
|||||||
"128": "icons/icon128.png"
|
"128": "icons/icon128.png"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"default_popup": "src/html/popup.html"
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab"
|
"activeTab",
|
||||||
|
"downloads",
|
||||||
|
"scripting",
|
||||||
|
"storage",
|
||||||
|
"tabs"
|
||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
"http://localhost:5000/*"
|
"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);
|
@ -2,9 +2,9 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="../css/styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
<script src="../../lib/qrcode.min.js"></script>
|
<script src="qrcode.min.js"></script>
|
||||||
<script src="../../lib/mammoth.browser.min.js"></script>
|
<script src="mammoth.browser.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -12,12 +12,15 @@
|
|||||||
<div class="tab">
|
<div class="tab">
|
||||||
<button class="tablinks active" data-tab="qr">
|
<button class="tablinks active" data-tab="qr">
|
||||||
<span class="icon">🔲</span>
|
<span class="icon">🔲</span>
|
||||||
|
二维码生成
|
||||||
</button>
|
</button>
|
||||||
<button class="tablinks" data-tab="word">
|
<button class="tablinks" data-tab="word">
|
||||||
<span class="icon">📄</span>
|
<span class="icon">📄</span>
|
||||||
|
Word转HTML
|
||||||
</button>
|
</button>
|
||||||
<button class="tablinks" data-tab="translate">
|
<button class="tablinks" data-tab="translate">
|
||||||
<span class="icon">🔄</span>
|
<span class="icon">🔤</span>
|
||||||
|
翻译设置
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -46,31 +49,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="translate" class="tabcontent" style="display:none;">
|
<div id="translate" class="tabcontent" style="display:none;">
|
||||||
<div class="file-upload">
|
<div class="translate-settings">
|
||||||
<label for="wordTranslateFile" class="file-label">
|
<div class="setting-item">
|
||||||
<span class="icon">📎</span>
|
<label class="switch">
|
||||||
<span id="translateFileNameDisplay">选择Word文件</span>
|
<input type="checkbox" id="translateEnabled">
|
||||||
|
<span class="slider"></span>
|
||||||
</label>
|
</label>
|
||||||
<input type="file" id="wordTranslateFile" accept=".docx" hidden>
|
<span class="setting-label">启用翻译功能 (Ctrl+Q)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="language-select">
|
<div class="setting-item">
|
||||||
|
<label>目标语言:</label>
|
||||||
<select id="targetLang">
|
<select id="targetLang">
|
||||||
<option value="en">英文</option>
|
<option value="en">英文</option>
|
||||||
<option value="jp">日文</option>
|
|
||||||
<option value="cht">繁体中文</option>
|
<option value="cht">繁体中文</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="translateProgress" class="progress-container">
|
|
||||||
<div class="progress-bar"></div>
|
|
||||||
</div>
|
|
||||||
<div id="progressText" class="progress-text"></div>
|
|
||||||
<div id="translateStatus" class="status-message"></div>
|
|
||||||
<button id="translateBtn" class="translate-btn" style="display:none;">
|
|
||||||
<span class="icon">🔄</span>
|
|
||||||
开始翻译
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="../js/popup.js"></script>
|
</div>
|
||||||
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
174
popup.js
Normal file
174
popup.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const urlInput = document.getElementById('urlInput');
|
||||||
|
const generateBtn = document.getElementById('generateBtn');
|
||||||
|
const qrcodeDiv = document.getElementById('qrcode');
|
||||||
|
|
||||||
|
const wordFile = document.getElementById('wordFile');
|
||||||
|
const preview = document.getElementById('preview');
|
||||||
|
const downloadBtn = document.getElementById('downloadBtn');
|
||||||
|
|
||||||
|
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.currentTarget.getAttribute('data-tab');
|
||||||
|
|
||||||
|
tabs.forEach(t => t.classList.remove('active'));
|
||||||
|
tab.classList.add('active');
|
||||||
|
|
||||||
|
tabContents.forEach(content => {
|
||||||
|
content.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedContent = document.getElementById(tabName);
|
||||||
|
if (selectedContent) {
|
||||||
|
selectedContent.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
|
||||||
|
if (tabs[0] && tabs[0].url) {
|
||||||
|
urlInput.value = tabs[0].url;
|
||||||
|
generateQRCode(tabs[0].url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
generateBtn.addEventListener('click', function() {
|
||||||
|
generateQRCode(urlInput.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
function generateQRCode(text) {
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
qrcodeDiv.innerHTML = '';
|
||||||
|
qr = new QRCode(qrcodeDiv, {
|
||||||
|
text: text,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
colorDark: "#000000",
|
||||||
|
colorLight: "#ffffff",
|
||||||
|
correctLevel: QRCode.CorrectLevel.H
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word转HTML功能
|
||||||
|
const fileNameDisplay = document.getElementById('fileNameDisplay');
|
||||||
|
const convertStatus = document.getElementById('convertStatus');
|
||||||
|
|
||||||
|
let currentFileName = '';
|
||||||
|
|
||||||
|
wordFile.addEventListener('change', function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
currentFileName = file.name.replace(/\.docx$/i, '');
|
||||||
|
fileNameDisplay.innerHTML = `
|
||||||
|
<span class="file-name" title="${file.name}">
|
||||||
|
📄 ${file.name}
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
showStatus('loading', '正在转换中...');
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
mammoth.convertToHtml({arrayBuffer: e.target.result})
|
||||||
|
.then(function(result) {
|
||||||
|
convertedHtml = result.value;
|
||||||
|
preview.innerHTML = convertedHtml;
|
||||||
|
downloadBtn.style.display = 'block';
|
||||||
|
showStatus('success', '转换成功!');
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error(error);
|
||||||
|
showStatus('error', '转换失败:' + error.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showStatus(type, message) {
|
||||||
|
convertStatus.className = 'status-message ' + type;
|
||||||
|
convertStatus.textContent = message;
|
||||||
|
|
||||||
|
if (type === 'success') {
|
||||||
|
setTimeout(() => {
|
||||||
|
convertStatus.style.display = 'none';
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadBtn.addEventListener('click', function() {
|
||||||
|
if (!convertedHtml) return;
|
||||||
|
|
||||||
|
const fullHtml = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>${currentFileName}</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; line-height: 1.6; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${convertedHtml}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const blob = new Blob([fullHtml], {type: 'text/html'});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
chrome.downloads.download({
|
||||||
|
url: url,
|
||||||
|
filename: `${currentFileName}.html`,
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
0
lib/qrcode.min.js → qrcode.min.js
vendored
0
lib/qrcode.min.js → qrcode.min.js
vendored
272
src/js/popup.js
272
src/js/popup.js
@ -1,272 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const urlInput = document.getElementById('urlInput');
|
|
||||||
const generateBtn = document.getElementById('generateBtn');
|
|
||||||
const qrcodeDiv = document.getElementById('qrcode');
|
|
||||||
|
|
||||||
const wordFile = document.getElementById('wordFile');
|
|
||||||
const preview = document.getElementById('preview');
|
|
||||||
const downloadBtn = document.getElementById('downloadBtn');
|
|
||||||
|
|
||||||
let qr = null;
|
|
||||||
let convertedHtml = '';
|
|
||||||
|
|
||||||
const tabs = document.querySelectorAll('.tablinks');
|
|
||||||
const tabContents = document.querySelectorAll('.tabcontent');
|
|
||||||
|
|
||||||
function switchTab(tabName) {
|
|
||||||
// 移除所有标签的激活状态
|
|
||||||
tabs.forEach(tab => tab.classList.remove('active'));
|
|
||||||
|
|
||||||
// 隐藏所有内容
|
|
||||||
tabContents.forEach(content => {
|
|
||||||
content.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 激活选中的标签
|
|
||||||
const selectedTab = document.querySelector(`[data-tab="${tabName}"]`);
|
|
||||||
if (selectedTab) {
|
|
||||||
selectedTab.classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示对应内容
|
|
||||||
const selectedContent = document.getElementById(tabName);
|
|
||||||
if (selectedContent) {
|
|
||||||
selectedContent.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为每个标签添加点击事件
|
|
||||||
tabs.forEach(tab => {
|
|
||||||
tab.addEventListener('click', (e) => {
|
|
||||||
const tabName = e.currentTarget.getAttribute('data-tab');
|
|
||||||
if (tabName) {
|
|
||||||
switchTab(tabName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化显示第一个标签页
|
|
||||||
switchTab('qr');
|
|
||||||
|
|
||||||
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
|
|
||||||
if (tabs[0] && tabs[0].url) {
|
|
||||||
urlInput.value = tabs[0].url;
|
|
||||||
generateQRCode(tabs[0].url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
generateBtn.addEventListener('click', function() {
|
|
||||||
generateQRCode(urlInput.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
function generateQRCode(text) {
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
qrcodeDiv.innerHTML = '';
|
|
||||||
qr = new QRCode(qrcodeDiv, {
|
|
||||||
text: text,
|
|
||||||
width: 256,
|
|
||||||
height: 256,
|
|
||||||
colorDark: "#000000",
|
|
||||||
colorLight: "#ffffff",
|
|
||||||
correctLevel: QRCode.CorrectLevel.H
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Word转HTML功能
|
|
||||||
const fileNameDisplay = document.getElementById('fileNameDisplay');
|
|
||||||
const convertStatus = document.getElementById('convertStatus');
|
|
||||||
|
|
||||||
let currentFileName = '';
|
|
||||||
|
|
||||||
wordFile.addEventListener('change', function(e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
currentFileName = file.name.replace(/\.docx$/i, '');
|
|
||||||
fileNameDisplay.innerHTML = `
|
|
||||||
<span class="file-name" title="${file.name}">
|
|
||||||
📄 ${file.name}
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
showStatus('loading', '正在转换中...');
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
mammoth.convertToHtml({arrayBuffer: e.target.result})
|
|
||||||
.then(function(result) {
|
|
||||||
convertedHtml = result.value;
|
|
||||||
preview.innerHTML = convertedHtml;
|
|
||||||
downloadBtn.style.display = 'block';
|
|
||||||
showStatus('success', '转换成功!');
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
console.error(error);
|
|
||||||
showStatus('error', '转换失败:' + error.message);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showStatus(type, message) {
|
|
||||||
convertStatus.className = 'status-message ' + type;
|
|
||||||
convertStatus.textContent = message;
|
|
||||||
|
|
||||||
if (type === 'success') {
|
|
||||||
setTimeout(() => {
|
|
||||||
convertStatus.style.display = 'none';
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadBtn.addEventListener('click', function() {
|
|
||||||
if (!convertedHtml) return;
|
|
||||||
|
|
||||||
const fullHtml = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>${currentFileName}</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; line-height: 1.6; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
${convertedHtml}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const blob = new Blob([fullHtml], {type: 'text/html'});
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
chrome.downloads.download({
|
|
||||||
url: url,
|
|
||||||
filename: `${currentFileName}.html`,
|
|
||||||
saveAs: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加百度翻译API配置
|
|
||||||
const BAIDU_APPID = '20241112002200806';
|
|
||||||
const BAIDU_KEY = 'preM0becByYCdotRTP_a';
|
|
||||||
|
|
||||||
let wordTranslateFile = null;
|
|
||||||
|
|
||||||
document.getElementById('wordTranslateFile').addEventListener('change', async (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
wordTranslateFile = file;
|
|
||||||
document.getElementById('translateFileNameDisplay').textContent = file.name;
|
|
||||||
document.getElementById('translateBtn').style.display = 'block';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('translateBtn').addEventListener('click', async () => {
|
|
||||||
if (!wordTranslateFile) return;
|
|
||||||
|
|
||||||
const translateBtn = document.getElementById('translateBtn');
|
|
||||||
const translateStatus = document.getElementById('translateStatus');
|
|
||||||
const progressBar = document.querySelector('.progress-bar');
|
|
||||||
const progressContainer = document.querySelector('.progress-container');
|
|
||||||
const progressText = document.getElementById('progressText');
|
|
||||||
|
|
||||||
// 禁用翻译按钮
|
|
||||||
translateBtn.disabled = true;
|
|
||||||
translateBtn.style.opacity = '0.6';
|
|
||||||
translateBtn.textContent = '翻译中...';
|
|
||||||
|
|
||||||
translateStatus.textContent = '正在处理文档...';
|
|
||||||
progressContainer.style.display = 'block';
|
|
||||||
progressText.style.display = 'block';
|
|
||||||
progressBar.style.width = '0%';
|
|
||||||
|
|
||||||
// 进度检查变量
|
|
||||||
let lastProgress = 0;
|
|
||||||
let noProgressCount = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', wordTranslateFile);
|
|
||||||
formData.append('target_lang', document.getElementById('targetLang').value);
|
|
||||||
|
|
||||||
// 开始翻译请求
|
|
||||||
const response = await fetch('http://172.16.100.22:8044/translate', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
// 定时检查进度
|
|
||||||
const progressCheck = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const progressResponse = await fetch('http://172.16.100.22:8044/progress');
|
|
||||||
const progressData = await progressResponse.json();
|
|
||||||
|
|
||||||
// 检查进度是否停滞
|
|
||||||
if (progressData.progress === lastProgress) {
|
|
||||||
noProgressCount++;
|
|
||||||
if (noProgressCount > 10) { // 5秒无进度更新
|
|
||||||
clearInterval(progressCheck);
|
|
||||||
throw new Error('翻译进度停滞,请重试');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
noProgressCount = 0;
|
|
||||||
lastProgress = progressData.progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
progressBar.style.width = `${progressData.progress}%`;
|
|
||||||
progressText.textContent = `翻译进度: ${progressData.progress}%`;
|
|
||||||
} catch (error) {
|
|
||||||
clearInterval(progressCheck);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
clearInterval(progressCheck);
|
|
||||||
const error = await response.json();
|
|
||||||
throw new Error(error.error || '翻译失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载翻译后的文件
|
|
||||||
const blob = await response.blob();
|
|
||||||
clearInterval(progressCheck);
|
|
||||||
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `translated_${wordTranslateFile.name}`;
|
|
||||||
a.click();
|
|
||||||
|
|
||||||
translateStatus.textContent = '翻译完成!';
|
|
||||||
progressBar.style.width = '100%';
|
|
||||||
|
|
||||||
// 重置按钮状态
|
|
||||||
setTimeout(() => {
|
|
||||||
translateBtn.disabled = false;
|
|
||||||
translateBtn.style.opacity = '1';
|
|
||||||
translateBtn.innerHTML = '<span class="icon">🔄</span>开始翻译';
|
|
||||||
progressContainer.style.display = 'none';
|
|
||||||
progressText.style.display = 'none';
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
translateStatus.textContent = '翻译失败:' + error.message;
|
|
||||||
progressContainer.style.display = 'none';
|
|
||||||
progressText.style.display = 'none';
|
|
||||||
|
|
||||||
// 重置按钮状态
|
|
||||||
translateBtn.disabled = false;
|
|
||||||
translateBtn.style.opacity = '1';
|
|
||||||
translateBtn.innerHTML = '<span class="icon">🔄</span>开始翻译';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// MD5函数实现
|
|
||||||
function MD5(string) {
|
|
||||||
// 实现MD5加密
|
|
||||||
// 可以使用第三方库如crypto-js
|
|
||||||
}
|
|
||||||
});
|
|
@ -10,7 +10,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 380px;
|
width: 400px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: white;
|
background: white;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
@ -25,34 +25,31 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
gap: 8px;
|
||||||
gap: 5px;
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
min-height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab button {
|
.tab button {
|
||||||
|
flex: 1;
|
||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 0;
|
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #666;
|
color: #666;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
white-space: nowrap;
|
||||||
|
min-width: 80px;
|
||||||
.tab button .icon {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab button:hover {
|
.tab button:hover {
|
||||||
@ -154,7 +151,8 @@ button:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 美化滚动条 */
|
/* 美化滚动条 */
|
||||||
@ -209,10 +207,11 @@ button:hover {
|
|||||||
|
|
||||||
.status-message {
|
.status-message {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 8px 12px;
|
padding: 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
text-align: center;
|
display: none;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-message.success {
|
.status-message.success {
|
||||||
@ -248,80 +247,71 @@ button:hover {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 添加新的样式 */
|
/* 翻译设置样式 */
|
||||||
.language-select {
|
.translate-settings {
|
||||||
margin: 10px 0;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-select select {
|
.setting-item {
|
||||||
width: 100%;
|
margin-bottom: 15px;
|
||||||
padding: 8px;
|
display: flex;
|
||||||
border-radius: 4px;
|
align-items: center;
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.translate-btn {
|
.setting-label {
|
||||||
width: 100%;
|
margin-left: 10px;
|
||||||
padding: 10px;
|
|
||||||
background: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.translate-btn:hover {
|
/* 开关按钮样式 */
|
||||||
background: #45a049;
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 添加进度条样式 */
|
.switch input {
|
||||||
.progress-container {
|
opacity: 0;
|
||||||
margin: 15px 0;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 4px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
height: 100%;
|
|
||||||
background: #1a73e8;
|
|
||||||
width: 0;
|
width: 0;
|
||||||
transition: width 0.3s ease;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 添加进度文本样式 */
|
.slider {
|
||||||
.progress-text {
|
position: absolute;
|
||||||
font-size: 12px;
|
cursor: pointer;
|
||||||
color: #666;
|
top: 0;
|
||||||
text-align: center;
|
left: 0;
|
||||||
margin-top: 5px;
|
right: 0;
|
||||||
display: none;
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修改选项卡文字样式 */
|
.slider:before {
|
||||||
.tab button[data-tab="qr"] {
|
position: absolute;
|
||||||
font-size: 12px;
|
content: "";
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: white;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab button[data-tab="qr"]::after {
|
input:checked + .slider {
|
||||||
content: "二维码生成";
|
background-color: #2196F3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab button[data-tab="word"] {
|
input:checked + .slider:before {
|
||||||
font-size: 12px;
|
transform: translateX(26px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab button[data-tab="word"]::after {
|
/* 下拉菜单样式 */
|
||||||
content: "Word转HTML";
|
select {
|
||||||
}
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
.tab button[data-tab="translate"] {
|
border: 1px solid #ddd;
|
||||||
font-size: 12px;
|
margin-left: 10px;
|
||||||
}
|
|
||||||
|
|
||||||
.tab button[data-tab="translate"]::after {
|
|
||||||
content: "Word翻译";
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user