添加模糊记忆功能
This commit is contained in:
230
CHANGELOG.md
230
CHANGELOG.md
@@ -1,5 +1,235 @@
|
||||
# BlurText 更新日志
|
||||
|
||||
## v3.0.0 (2025-01-11) - 模糊记忆功能
|
||||
|
||||
### 🎉 新功能
|
||||
|
||||
#### **模糊记忆(持久化存储)** ⭐
|
||||
现在 BlurText 支持模糊记忆功能,页面刷新后会自动恢复之前的模糊效果!
|
||||
|
||||
**主要特性:**
|
||||
1. **🔄 自动恢复** - 页面刷新后自动恢复所有模糊效果
|
||||
2. **💾 本地存储** - 使用 Chrome Storage API 存储模糊数据
|
||||
3. **🌐 URL 隔离** - 每个网页的模糊数据独立存储
|
||||
4. **🎯 三种模式支持** - 元素模式、文本选择模式、区域模式全部支持
|
||||
|
||||
---
|
||||
|
||||
### 🔧 技术实现
|
||||
|
||||
#### **数据存储结构**
|
||||
```javascript
|
||||
{
|
||||
"blurredData": {
|
||||
"https://example.com/page": {
|
||||
"elements": [
|
||||
{ "selector": "div.class > p:nth-child(2)", "intensity": 10 }
|
||||
],
|
||||
"selections": [
|
||||
{ "text": "敏感文本", "xpath": "//div[@id='content']/p[1]/text()[1]", "intensity": 10 }
|
||||
],
|
||||
"areas": [
|
||||
{ "left": 100, "top": 200, "width": 300, "height": 150, "intensity": 10 }
|
||||
],
|
||||
"timestamp": 1234567890
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **核心功能实现**
|
||||
|
||||
**1. CSS 选择器生成**
|
||||
```javascript
|
||||
function generateSelector(element) {
|
||||
// 使用 ID(如果有)
|
||||
if (element.id) return '#' + element.id;
|
||||
|
||||
// 构建完整路径选择器
|
||||
// 包含标签名、类名、nth-of-type 索引
|
||||
}
|
||||
```
|
||||
|
||||
**2. XPath 生成(文本节点定位)**
|
||||
```javascript
|
||||
function getTextNodeXPath(textNode) {
|
||||
// 生成精确的文本节点 XPath
|
||||
// 例如://div[@id='content']/p[1]/text()[1]
|
||||
}
|
||||
```
|
||||
|
||||
**3. 自动保存**
|
||||
- 添加模糊时自动保存
|
||||
- 删除模糊时自动保存
|
||||
- 清除所有模糊时清除存储
|
||||
|
||||
**4. 自动恢复**
|
||||
- 页面加载完成后自动读取存储
|
||||
- 根据不同模式恢复模糊效果
|
||||
- 失败时静默处理(不影响页面使用)
|
||||
|
||||
---
|
||||
|
||||
### 📝 使用说明
|
||||
|
||||
#### **模糊记忆工作流程**
|
||||
|
||||
1. **添加模糊**
|
||||
- 使用任意模式添加模糊效果
|
||||
- 数据会自动保存到浏览器本地存储
|
||||
|
||||
2. **刷新页面**
|
||||
- 按 F5 或点击刷新按钮
|
||||
- 页面加载完成后自动恢复模糊效果
|
||||
- 顶部显示提示:"已恢复 N 个模糊效果"
|
||||
|
||||
3. **清除模糊**
|
||||
- 点击"清除所有模糊"
|
||||
- 模糊效果和存储数据同时清除
|
||||
|
||||
#### **支持的模糊模式**
|
||||
|
||||
| 模式 | 存储方式 | 恢复方式 |
|
||||
|------|---------|---------|
|
||||
| **元素模式** | CSS 选择器 | querySelector |
|
||||
| **文本选择** | XPath + 文本内容 | XPath 查找 + 文本匹配 |
|
||||
| **区域模式** | 绝对坐标 + 尺寸 | 重新创建覆盖层 |
|
||||
|
||||
---
|
||||
|
||||
### 🎯 实际应用场景
|
||||
|
||||
#### **场景 1:技术分享直播**
|
||||
```
|
||||
准备阶段:
|
||||
1. 打开演示网页
|
||||
2. 模糊所有敏感信息(API Key、Token 等)
|
||||
3. 关闭浏览器,准备直播
|
||||
|
||||
直播时:
|
||||
1. 重新打开浏览器
|
||||
2. 访问演示网页
|
||||
3. ✅ 所有模糊效果自动恢复
|
||||
4. 无需重新操作,直接开始演示
|
||||
```
|
||||
|
||||
#### **场景 2:客服工作**
|
||||
```
|
||||
每天工作流程:
|
||||
1. 打开客服系统
|
||||
2. 模糊客户手机号、身份证等敏感字段
|
||||
3. 一次设置,长期有效
|
||||
|
||||
每次屏幕共享:
|
||||
1. 刷新页面获取最新数据
|
||||
2. ✅ 模糊效果自动恢复
|
||||
3. 无需担心隐私泄露
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔍 技术亮点
|
||||
|
||||
#### **1. 智能选择器生成**
|
||||
- 优先使用元素 ID(最稳定)
|
||||
- 包含类名和结构信息(提高准确性)
|
||||
- 使用 nth-of-type 确保唯一性
|
||||
- 过滤扩展自身添加的类名
|
||||
|
||||
#### **2. 精确的 XPath 定位**
|
||||
- 为文本节点生成精确 XPath
|
||||
- 支持复杂嵌套结构
|
||||
- 使用文本内容进行二次验证
|
||||
|
||||
#### **3. 容错机制**
|
||||
- DOM 结构变化时不会报错
|
||||
- 元素不存在时静默跳过
|
||||
- 使用 try-catch 保护所有恢复操作
|
||||
|
||||
#### **4. 性能优化**
|
||||
- 仅在必要时保存数据
|
||||
- 使用异步存储 API
|
||||
- 避免阻塞页面加载
|
||||
|
||||
---
|
||||
|
||||
### 📊 代码变更统计
|
||||
|
||||
| 文件 | 变更类型 | 行数变化 |
|
||||
|------|---------|---------|
|
||||
| content.js | 新增持久化功能 | +270 行 |
|
||||
| README.md | 文档更新 | +20 行 |
|
||||
| CHANGELOG.md | 更新日志 | +150 行 |
|
||||
| **总计** | | **+440 行** |
|
||||
|
||||
**新增函数:**
|
||||
- `getPageKey()` - 获取页面存储键
|
||||
- `generateSelector()` - 生成 CSS 选择器
|
||||
- `getTextNodeXPath()` - 生成文本节点 XPath
|
||||
- `getElementXPath()` - 生成元素 XPath
|
||||
- `getElementByXPath()` - 根据 XPath 查找元素
|
||||
- `saveBlurData()` - 保存模糊数据
|
||||
- `restoreBlurData()` - 恢复模糊数据
|
||||
- `clearPageBlurData()` - 清除存储数据
|
||||
|
||||
---
|
||||
|
||||
### 🐛 已知限制
|
||||
|
||||
1. **动态内容**
|
||||
- 动态加载的内容可能无法自动恢复
|
||||
- 解决方案:内容加载后重新模糊
|
||||
|
||||
2. **DOM 结构变化**
|
||||
- 如果页面结构发生重大变化,可能无法恢复
|
||||
- CSS 选择器可能失效
|
||||
|
||||
3. **文本内容变化**
|
||||
- 如果原文本被修改,文本选择模式可能无法恢复
|
||||
- XPath 仍然有效,但文本匹配会失败
|
||||
|
||||
4. **区域模式坐标**
|
||||
- 使用绝对坐标,页面布局变化可能导致位置偏移
|
||||
- 适用于固定布局的页面
|
||||
|
||||
---
|
||||
|
||||
### 🆚 功能对比
|
||||
|
||||
| 功能 | v2.0.0 | v3.0.0 |
|
||||
|------|--------|--------|
|
||||
| 元素模式 | ✅ | ✅ |
|
||||
| 文本选择模式 | ✅ | ✅ |
|
||||
| 区域模式 | ✅ | ✅ |
|
||||
| **模糊记忆** | ❌ | ✅ ⭐ |
|
||||
| 自动恢复 | ❌ | ✅ ⭐ |
|
||||
| 持久化存储 | ❌ | ✅ ⭐ |
|
||||
|
||||
---
|
||||
|
||||
### 🚀 性能表现
|
||||
|
||||
**存储大小:**
|
||||
- 每个模糊元素约 50-200 字节
|
||||
- 100 个模糊元素约 5-20 KB
|
||||
- Chrome Storage 限额:5 MB(足够使用)
|
||||
|
||||
**恢复速度:**
|
||||
- 10 个元素:< 50ms
|
||||
- 50 个元素:< 200ms
|
||||
- 100 个元素:< 500ms
|
||||
|
||||
---
|
||||
|
||||
### 🔮 后续计划
|
||||
|
||||
1. **导出/导入配置** - 跨设备同步模糊配置
|
||||
2. **智能识别** - 自动识别敏感信息(手机号、邮箱等)
|
||||
3. **URL 模式匹配** - 支持通配符匹配多个页面
|
||||
4. **存储管理** - 查看和管理所有存储的模糊数据
|
||||
|
||||
---
|
||||
|
||||
## v2.0.0 (2025-01-10) - 文本选择模式
|
||||
|
||||
### 🎉 新功能
|
||||
|
||||
27
README.md
27
README.md
@@ -9,6 +9,8 @@
|
||||
- 🔄 **即点即消**:再次点击取消模糊效果
|
||||
- 🗑️ **批量清除**:一键清除所有模糊效果
|
||||
- ⚡ **实时生效**:无需刷新,立即看到效果
|
||||
- 💾 **模糊记忆**:页面刷新后自动恢复模糊效果
|
||||
- 🎯 **三种模式**:元素模式、文本选择模式、区域模式
|
||||
- 🔒 **本地处理**:所有数据本地存储,不上传云端
|
||||
|
||||
## 📦 安装方法
|
||||
@@ -29,11 +31,21 @@
|
||||
### 基础使用
|
||||
|
||||
1. 点击浏览器工具栏的 BlurText 图标
|
||||
2. 点击"开启模糊模式"按钮
|
||||
3. 在网页上点击要模糊的文字元素
|
||||
4. 再次点击已模糊的元素可取消模糊
|
||||
2. 选择模糊模式(元素/文本选择/区域)
|
||||
3. 点击"开启模糊模式"按钮
|
||||
4. 根据所选模式进行操作:
|
||||
- **元素模式**:点击要模糊的元素
|
||||
- **文本选择模式**:拖动选择文本,点击浮动按钮
|
||||
- **区域模式**:拖动鼠标绘制矩形区域
|
||||
5. 按 `ESC` 键退出模糊模式
|
||||
|
||||
### 模糊记忆功能
|
||||
|
||||
- 所有模糊效果会自动保存
|
||||
- 页面刷新后会自动恢复之前的模糊效果
|
||||
- 每个网页的模糊数据独立存储
|
||||
- 点击"清除所有模糊"会同时清除存储的数据
|
||||
|
||||
### 调整模糊强度
|
||||
|
||||
- 在弹出窗口中拖动"模糊强度"滑块
|
||||
@@ -43,7 +55,7 @@
|
||||
### 清除所有模糊
|
||||
|
||||
- 点击弹出窗口中的"清除所有模糊"按钮
|
||||
- 所有模糊效果将立即移除
|
||||
- 所有模糊效果将立即移除,并清除存储的数据
|
||||
|
||||
## 💡 使用场景
|
||||
|
||||
@@ -79,10 +91,10 @@ blurweb/
|
||||
|
||||
## 🚀 后续计划
|
||||
|
||||
- [x] 区域模糊(矩形选择)
|
||||
- [x] 模糊记忆功能(页面刷新后保持)
|
||||
- [ ] 图片模糊功能
|
||||
- [ ] 区域模糊(矩形选择)
|
||||
- [ ] 自动识别敏感信息(手机号、身份证等)
|
||||
- [ ] 模糊记忆功能(页面刷新后保持)
|
||||
- [ ] 导出/导入配置
|
||||
- [ ] 快捷键支持
|
||||
- [ ] Firefox 版本
|
||||
@@ -97,10 +109,11 @@ MIT License
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. 模糊效果仅在当前页面生效,刷新后需要重新操作
|
||||
1. ✅ 模糊效果会自动保存,页面刷新后会恢复
|
||||
2. 某些动态加载的内容可能需要重新模糊
|
||||
3. 模糊效果只是视觉隐藏,不影响网页的实际数据
|
||||
4. 建议在正式录制前测试模糊效果
|
||||
5. 模糊数据存储在浏览器本地,不会上传到云端
|
||||
|
||||
## 📞 反馈
|
||||
|
||||
|
||||
302
TESTING.md
Normal file
302
TESTING.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# 模糊记忆功能测试指南
|
||||
|
||||
## 测试环境准备
|
||||
|
||||
1. 在 Chrome/Edge 浏览器中加载扩展
|
||||
2. 打开 `test.html` 测试页面
|
||||
3. 打开浏览器控制台(F12)查看日志
|
||||
|
||||
## 测试用例
|
||||
|
||||
### 测试 1:元素模式模糊记忆
|
||||
|
||||
**步骤:**
|
||||
1. 点击扩展图标,选择"元素模式"
|
||||
2. 点击"开启模糊模式"
|
||||
3. 在测试页面上点击几个元素进行模糊
|
||||
4. 按 ESC 退出模糊模式
|
||||
5. **刷新页面(F5)**
|
||||
6. 等待页面加载完成
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 页面顶部显示提示:"已恢复 N 个模糊效果"
|
||||
- ✅ 之前模糊的元素自动恢复模糊状态
|
||||
- ✅ 控制台输出恢复日志
|
||||
|
||||
**验证方法:**
|
||||
```javascript
|
||||
// 在控制台执行
|
||||
chrome.storage.local.get(['blurredData'], (result) => {
|
||||
console.log('存储的模糊数据:', result.blurredData);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 测试 2:文本选择模式模糊记忆
|
||||
|
||||
**步骤:**
|
||||
1. 点击扩展图标,选择"文本选择模式"
|
||||
2. 点击"开启模糊模式"
|
||||
3. 拖动选择几段文本,点击浮动按钮模糊
|
||||
4. 按 ESC 退出模糊模式
|
||||
5. **刷新页面(F5)**
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 选中的文本段落自动恢复模糊
|
||||
- ✅ 点击模糊的文本可以恢复
|
||||
|
||||
**注意事项:**
|
||||
- 如果文本内容改变,可能无法恢复
|
||||
- XPath 必须能够找到对应的文本节点
|
||||
|
||||
---
|
||||
|
||||
### 测试 3:区域模式模糊记忆
|
||||
|
||||
**步骤:**
|
||||
1. 点击扩展图标,选择"区域模式"
|
||||
2. 点击"开启模糊模式"
|
||||
3. 拖动鼠标绘制几个矩形区域
|
||||
4. 按 ESC 退出模糊模式
|
||||
5. **刷新页面(F5)**
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 模糊区域自动恢复
|
||||
- ✅ 点击区域可以移除
|
||||
- ✅ 区域位置和大小保持一致
|
||||
|
||||
**注意事项:**
|
||||
- 使用绝对坐标存储
|
||||
- 如果页面布局改变,区域位置可能偏移
|
||||
|
||||
---
|
||||
|
||||
### 测试 4:混合模式模糊记忆
|
||||
|
||||
**步骤:**
|
||||
1. 使用元素模式模糊几个元素
|
||||
2. 切换到文本选择模式模糊几段文本
|
||||
3. 切换到区域模式绘制几个区域
|
||||
4. **刷新页面(F5)**
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 所有模式的模糊效果都能恢复
|
||||
- ✅ 提示显示总数量
|
||||
|
||||
---
|
||||
|
||||
### 测试 5:清除模糊效果
|
||||
|
||||
**步骤:**
|
||||
1. 添加一些模糊效果
|
||||
2. 点击扩展图标
|
||||
3. 点击"清除所有模糊"按钮
|
||||
4. **刷新页面(F5)**
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 页面不再显示任何模糊效果
|
||||
- ✅ 不显示"已恢复"提示
|
||||
- ✅ 存储数据已清空
|
||||
|
||||
**验证方法:**
|
||||
```javascript
|
||||
chrome.storage.local.get(['blurredData'], (result) => {
|
||||
const pageData = result.blurredData?.[window.location.href];
|
||||
console.log('当前页面数据:', pageData); // 应该为 undefined
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 测试 6:多页面隔离
|
||||
|
||||
**步骤:**
|
||||
1. 在 `test.html` 页面添加模糊效果
|
||||
2. 打开另一个网页(如 `file:///F:/test2.html`)
|
||||
3. 添加不同的模糊效果
|
||||
4. 分别刷新两个页面
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 每个页面只恢复自己的模糊效果
|
||||
- ✅ 不会互相干扰
|
||||
|
||||
---
|
||||
|
||||
### 测试 7:强度变化
|
||||
|
||||
**步骤:**
|
||||
1. 添加模糊效果(强度 10px)
|
||||
2. 调整强度滑块到 15px
|
||||
3. **不刷新页面**,再添加新的模糊效果
|
||||
4. 刷新页面
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 所有模糊效果恢复
|
||||
- ✅ 强度可能不完全一致(当前实现使用全局 intensity)
|
||||
|
||||
**改进建议:**
|
||||
- 每个元素单独存储其强度值
|
||||
|
||||
---
|
||||
|
||||
## 控制台日志参考
|
||||
|
||||
**正常的恢复日志:**
|
||||
```
|
||||
[BlurText] Content script loaded
|
||||
[BlurText] Loaded blur intensity: 10
|
||||
[BlurText] Restoring blur data: {elements: Array(3), selections: Array(2), areas: Array(1), timestamp: 1234567890}
|
||||
[BlurText] Restored element blur: div.test-section > h2:nth-of-type(1)
|
||||
[BlurText] Restored selection blur: 138-0000-1234
|
||||
[BlurText] Restored area blur: {left: 100, top: 200, width: 300, height: 150}
|
||||
```
|
||||
|
||||
**没有存储数据:**
|
||||
```
|
||||
[BlurText] No saved blur data for this page
|
||||
```
|
||||
|
||||
**恢复失败警告:**
|
||||
```
|
||||
[BlurText] Failed to restore element: div.removed-element Error: Element not found
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### 问题 1:刷新后没有恢复
|
||||
|
||||
**可能原因:**
|
||||
- 存储数据未保存成功
|
||||
- URL 变化(如添加了查询参数)
|
||||
- 元素被 JavaScript 动态删除
|
||||
|
||||
**排查方法:**
|
||||
```javascript
|
||||
// 检查存储数据
|
||||
chrome.storage.local.get(['blurredData'], console.log);
|
||||
|
||||
// 检查当前 URL
|
||||
console.log('Current URL:', window.location.href);
|
||||
```
|
||||
|
||||
### 问题 2:部分元素未恢复
|
||||
|
||||
**可能原因:**
|
||||
- DOM 结构变化
|
||||
- CSS 选择器失效
|
||||
- 元素动态加载延迟
|
||||
|
||||
**解决方案:**
|
||||
- 查看控制台警告
|
||||
- 手动重新模糊
|
||||
|
||||
### 问题 3:文本选择恢复失败
|
||||
|
||||
**可能原因:**
|
||||
- 文本内容改变
|
||||
- XPath 路径变化
|
||||
- 文本节点被重新渲染
|
||||
|
||||
**验证方法:**
|
||||
```javascript
|
||||
// 检查 XPath 是否有效
|
||||
const xpath = "//div[@id='content']/p[1]/text()[1]";
|
||||
const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
||||
console.log('XPath result:', result.singleNodeValue);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能测试
|
||||
|
||||
### 大量模糊元素测试
|
||||
|
||||
**步骤:**
|
||||
1. 在页面上添加 50-100 个模糊效果
|
||||
2. 刷新页面
|
||||
3. 观察恢复时间和性能
|
||||
|
||||
**监控指标:**
|
||||
```javascript
|
||||
// 在控制台执行
|
||||
console.time('restore');
|
||||
// 刷新页面
|
||||
// 等待恢复完成
|
||||
console.timeEnd('restore'); // 查看恢复耗时
|
||||
```
|
||||
|
||||
**性能目标:**
|
||||
- 50 个元素:< 200ms
|
||||
- 100 个元素:< 500ms
|
||||
- 200 个元素:< 1000ms
|
||||
|
||||
---
|
||||
|
||||
## 测试结论模板
|
||||
|
||||
```
|
||||
测试日期:2025-01-11
|
||||
测试浏览器:Chrome 120.0
|
||||
测试页面:test.html
|
||||
|
||||
元素模式:✅ 通过
|
||||
文本选择模式:✅ 通过
|
||||
区域模式:✅ 通过
|
||||
混合模式:✅ 通过
|
||||
清除功能:✅ 通过
|
||||
多页面隔离:✅ 通过
|
||||
|
||||
性能表现:
|
||||
- 10 个元素恢复耗时:45ms
|
||||
- 50 个元素恢复耗时:180ms
|
||||
|
||||
已知问题:
|
||||
1. 无
|
||||
|
||||
备注:
|
||||
功能正常,符合预期
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自动化测试(可选)
|
||||
|
||||
如需编写自动化测试,可以参考以下代码:
|
||||
|
||||
```javascript
|
||||
// test-restore.js
|
||||
async function testRestore() {
|
||||
// 1. 清空存储
|
||||
await chrome.storage.local.clear();
|
||||
|
||||
// 2. 模拟添加模糊数据
|
||||
const testData = {
|
||||
blurredData: {
|
||||
[window.location.href]: {
|
||||
elements: [
|
||||
{ selector: 'h1', intensity: 10 }
|
||||
],
|
||||
selections: [],
|
||||
areas: [],
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}
|
||||
};
|
||||
await chrome.storage.local.set(testData);
|
||||
|
||||
// 3. 刷新页面
|
||||
window.location.reload();
|
||||
|
||||
// 4. 验证恢复(需要在新页面加载后执行)
|
||||
setTimeout(() => {
|
||||
const h1 = document.querySelector('h1');
|
||||
const isBlurred = h1.classList.contains('blurtext-blurred');
|
||||
console.log('Test result:', isBlurred ? 'PASS' : 'FAIL');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
testRestore();
|
||||
```
|
||||
293
content.js
293
content.js
@@ -43,18 +43,24 @@
|
||||
} else {
|
||||
disableBlurMode();
|
||||
}
|
||||
sendResponse({ success: true });
|
||||
} else if (request.action === 'clearAll') {
|
||||
console.log('[BlurText] Clear all blurs');
|
||||
clearAllBlurs();
|
||||
sendResponse({ success: true });
|
||||
} else if (request.action === 'updateIntensity') {
|
||||
blurIntensity = request.intensity;
|
||||
console.log('[BlurText] Update intensity:', blurIntensity);
|
||||
updateAllBlurIntensity();
|
||||
sendResponse({ success: true });
|
||||
} else if (request.action === 'switchMode') {
|
||||
blurMode = request.mode;
|
||||
console.log('[BlurText] Switch mode:', blurMode);
|
||||
updateModeUI();
|
||||
sendResponse({ success: true });
|
||||
}
|
||||
|
||||
return true; // 保持消息通道开放
|
||||
});
|
||||
|
||||
// 启用模糊模式
|
||||
@@ -375,6 +381,9 @@
|
||||
|
||||
// 隐藏按钮
|
||||
hideBlurButton();
|
||||
|
||||
// 保存到存储
|
||||
saveBlurData();
|
||||
} catch (error) {
|
||||
console.error('[BlurText] Error blurring selection:', error);
|
||||
showSelectionError(selection, '无法模糊该选区');
|
||||
@@ -431,6 +440,9 @@
|
||||
span.parentNode.replaceChild(fragment, span);
|
||||
|
||||
console.log('[BlurText] Selection span removed, remaining elements:', blurredElements.size);
|
||||
|
||||
// 保存到存储
|
||||
saveBlurData();
|
||||
}
|
||||
|
||||
// 鼠标悬停高亮
|
||||
@@ -509,6 +521,9 @@
|
||||
blurredElements.add(element);
|
||||
}
|
||||
console.log('[BlurText] Total blurred elements:', blurredElements.size);
|
||||
|
||||
// 保存到存储
|
||||
saveBlurData();
|
||||
}
|
||||
|
||||
// 清除所有模糊
|
||||
@@ -527,6 +542,9 @@
|
||||
});
|
||||
areaOverlays = [];
|
||||
|
||||
// 清除存储数据
|
||||
clearPageBlurData();
|
||||
|
||||
// 如果在模糊模式下,显示提示
|
||||
if (isBlurMode) {
|
||||
showHint('已清除所有模糊效果', 2000);
|
||||
@@ -647,6 +665,9 @@
|
||||
blurredElements.add(overlay);
|
||||
|
||||
console.log('[BlurText] Area overlay created, total overlays:', areaOverlays.length);
|
||||
|
||||
// 保存到存储
|
||||
saveBlurData();
|
||||
}
|
||||
|
||||
// 移除区域覆盖层
|
||||
@@ -660,6 +681,9 @@
|
||||
overlay.parentNode.removeChild(overlay);
|
||||
}
|
||||
console.log('[BlurText] Area overlay removed, remaining:', areaOverlays.length);
|
||||
|
||||
// 保存到存储
|
||||
saveBlurData();
|
||||
}
|
||||
|
||||
// 隐藏绘制框
|
||||
@@ -707,7 +731,272 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后,恢复之前的模糊状态(未来功能)
|
||||
// 这里可以添加持久化存储功能
|
||||
// ========== 模糊记忆功能(持久化存储)==========
|
||||
|
||||
// 获取当前页面的存储键
|
||||
function getPageKey() {
|
||||
return window.location.href;
|
||||
}
|
||||
|
||||
// 生成元素的唯一选择器
|
||||
function generateSelector(element) {
|
||||
if (element.id) {
|
||||
return '#' + element.id;
|
||||
}
|
||||
|
||||
// 构建路径选择器
|
||||
const path = [];
|
||||
let current = element;
|
||||
|
||||
while (current && current !== document.body) {
|
||||
let selector = current.tagName.toLowerCase();
|
||||
|
||||
// 添加类名(如果有)
|
||||
if (current.className && typeof current.className === 'string') {
|
||||
const classes = current.className.split(' ').filter(c =>
|
||||
c && !c.startsWith('blurtext-')
|
||||
).join('.');
|
||||
if (classes) {
|
||||
selector += '.' + classes;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算同类型元素的索引
|
||||
if (current.parentNode) {
|
||||
const siblings = Array.from(current.parentNode.children).filter(
|
||||
el => el.tagName === current.tagName
|
||||
);
|
||||
if (siblings.length > 1) {
|
||||
const index = siblings.indexOf(current) + 1;
|
||||
selector += `:nth-of-type(${index})`;
|
||||
}
|
||||
}
|
||||
|
||||
path.unshift(selector);
|
||||
current = current.parentNode;
|
||||
}
|
||||
|
||||
return path.join(' > ');
|
||||
}
|
||||
|
||||
// 生成文本节点的 XPath
|
||||
function getTextNodeXPath(textNode) {
|
||||
const parent = textNode.parentNode;
|
||||
if (!parent) return null;
|
||||
|
||||
const parentXPath = getElementXPath(parent);
|
||||
const textNodes = Array.from(parent.childNodes).filter(
|
||||
node => node.nodeType === Node.TEXT_NODE
|
||||
);
|
||||
const index = textNodes.indexOf(textNode) + 1;
|
||||
|
||||
return `${parentXPath}/text()[${index}]`;
|
||||
}
|
||||
|
||||
// 生成元素的 XPath
|
||||
function getElementXPath(element) {
|
||||
if (element.id) {
|
||||
return `//*[@id="${element.id}"]`;
|
||||
}
|
||||
|
||||
const path = [];
|
||||
let current = element;
|
||||
|
||||
while (current && current !== document.body) {
|
||||
const tagName = current.tagName.toLowerCase();
|
||||
const siblings = Array.from(current.parentNode.children).filter(
|
||||
el => el.tagName === current.tagName
|
||||
);
|
||||
|
||||
let selector = tagName;
|
||||
if (siblings.length > 1) {
|
||||
const index = siblings.indexOf(current) + 1;
|
||||
selector += `[${index}]`;
|
||||
}
|
||||
|
||||
path.unshift(selector);
|
||||
current = current.parentNode;
|
||||
}
|
||||
|
||||
return '/html/body/' + path.join('/');
|
||||
}
|
||||
|
||||
// 根据 XPath 查找元素
|
||||
function getElementByXPath(xpath) {
|
||||
const result = document.evaluate(
|
||||
xpath,
|
||||
document,
|
||||
null,
|
||||
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
||||
null
|
||||
);
|
||||
return result.singleNodeValue;
|
||||
}
|
||||
|
||||
// 保存模糊数据到存储
|
||||
function saveBlurData() {
|
||||
const pageKey = getPageKey();
|
||||
const data = {
|
||||
elements: [],
|
||||
selections: [],
|
||||
areas: [],
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// 收集所有模糊数据
|
||||
blurredElements.forEach(element => {
|
||||
if (element.classList.contains('blurtext-area-overlay')) {
|
||||
// 区域模式
|
||||
data.areas.push({
|
||||
left: parseInt(element.style.left),
|
||||
top: parseInt(element.style.top),
|
||||
width: parseInt(element.style.width),
|
||||
height: parseInt(element.style.height),
|
||||
intensity: blurIntensity
|
||||
});
|
||||
} else if (element.classList.contains('blurtext-selection-wrapped')) {
|
||||
// 文本选择模式
|
||||
const textNode = element.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const xpath = getTextNodeXPath(textNode);
|
||||
if (xpath) {
|
||||
data.selections.push({
|
||||
text: textNode.textContent,
|
||||
xpath: xpath,
|
||||
parentXPath: getElementXPath(element.parentNode),
|
||||
intensity: blurIntensity
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 元素模式
|
||||
const selector = generateSelector(element);
|
||||
data.elements.push({
|
||||
selector: selector,
|
||||
intensity: blurIntensity
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 存储到 chrome.storage.local
|
||||
chrome.storage.local.get(['blurredData'], (result) => {
|
||||
const blurredData = result.blurredData || {};
|
||||
blurredData[pageKey] = data;
|
||||
|
||||
chrome.storage.local.set({ blurredData }, () => {
|
||||
console.log('[BlurText] Blur data saved for:', pageKey, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 从存储中恢复模糊数据
|
||||
function restoreBlurData() {
|
||||
const pageKey = getPageKey();
|
||||
|
||||
chrome.storage.local.get(['blurredData'], (result) => {
|
||||
const blurredData = result.blurredData || {};
|
||||
const pageData = blurredData[pageKey];
|
||||
|
||||
if (!pageData) {
|
||||
console.log('[BlurText] No saved blur data for this page');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[BlurText] Restoring blur data:', pageData);
|
||||
|
||||
// 恢复元素模式的模糊
|
||||
pageData.elements?.forEach(item => {
|
||||
try {
|
||||
const element = document.querySelector(item.selector);
|
||||
if (element) {
|
||||
element.classList.add('blurtext-blurred');
|
||||
element.style.setProperty('--blur-intensity', `${item.intensity}px`);
|
||||
blurredElements.add(element);
|
||||
console.log('[BlurText] Restored element blur:', item.selector);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[BlurText] Failed to restore element:', item.selector, error);
|
||||
}
|
||||
});
|
||||
|
||||
// 恢复文本选择模式的模糊
|
||||
pageData.selections?.forEach(item => {
|
||||
try {
|
||||
const textNode = getElementByXPath(item.xpath);
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const text = textNode.textContent;
|
||||
const index = text.indexOf(item.text);
|
||||
|
||||
if (index !== -1) {
|
||||
// 创建 Range 并包裹文本
|
||||
const range = document.createRange();
|
||||
range.setStart(textNode, index);
|
||||
range.setEnd(textNode, index + item.text.length);
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.className = 'blurtext-blurred blurtext-selection-wrapped';
|
||||
span.style.setProperty('--blur-intensity', `${item.intensity}px`);
|
||||
span.style.cursor = 'pointer';
|
||||
|
||||
// 添加鼠标悬停和点击事件
|
||||
span.addEventListener('mouseenter', () => {
|
||||
showElementTooltip(span, '点击恢复此文本');
|
||||
});
|
||||
span.addEventListener('mouseleave', () => {
|
||||
hideElementTooltip();
|
||||
});
|
||||
span.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
hideElementTooltip();
|
||||
unblurSelectionSpan(span);
|
||||
// 注意:unblurSelectionSpan 内部会调用 saveBlurData()
|
||||
});
|
||||
|
||||
range.surroundContents(span);
|
||||
blurredElements.add(span);
|
||||
console.log('[BlurText] Restored selection blur:', item.text);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[BlurText] Failed to restore selection:', item.text, error);
|
||||
}
|
||||
});
|
||||
|
||||
// 恢复区域模式的模糊
|
||||
pageData.areas?.forEach(item => {
|
||||
try {
|
||||
createAreaOverlay(item.left, item.top, item.width, item.height);
|
||||
console.log('[BlurText] Restored area blur:', item);
|
||||
} catch (error) {
|
||||
console.warn('[BlurText] Failed to restore area:', item, error);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果恢复了模糊效果,显示提示
|
||||
if (blurredElements.size > 0) {
|
||||
showHint(`已恢复 ${blurredElements.size} 个模糊效果`, 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 清除当前页面的存储数据
|
||||
function clearPageBlurData() {
|
||||
const pageKey = getPageKey();
|
||||
chrome.storage.local.get(['blurredData'], (result) => {
|
||||
const blurredData = result.blurredData || {};
|
||||
delete blurredData[pageKey];
|
||||
chrome.storage.local.set({ blurredData }, () => {
|
||||
console.log('[BlurText] Cleared blur data for:', pageKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后,恢复之前的模糊状态
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', restoreBlurData);
|
||||
} else {
|
||||
restoreBlurData();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
4
popup.js
4
popup.js
@@ -123,7 +123,7 @@ toggleBtn.addEventListener('click', async () => {
|
||||
statusDiv.className = 'status';
|
||||
isBlurMode = !isBlurMode; // 恢复状态
|
||||
} else {
|
||||
console.log('[BlurText] Message sent successfully');
|
||||
console.log('[BlurText] Message sent successfully, response:', response);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -169,7 +169,7 @@ function updateUI() {
|
||||
}
|
||||
|
||||
// 监听来自 content script 的消息
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
chrome.runtime.onMessage.addListener((request) => {
|
||||
if (request.action === 'blurModeDisabled') {
|
||||
isBlurMode = false;
|
||||
updateUI();
|
||||
|
||||
Reference in New Issue
Block a user