paper-burner/output/BUGFIX_CROSS_BLOCK_ANNOTATI...

338 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bug修复: 跨子块批注状态污染
> **修复日期**: 2025-11-12
> **严重程度**: 🔴 高(影响批注核心功能)
> **影响范围**: 批注和高亮系统
> **修复文件**: `js/annotations/annotation_logic.js`
---
## 🐛 Bug 描述
### 问题表现
用户在详情页进行批注操作时:
1. 先做一次**跨子块高亮**(比如选中 8 个子块)
2. 再做**单子块内高亮**(比如只选中 109.0 内的文本)
3. **Bug**: 第 2 次操作却执行了跨 8 个子块的高亮 ❌
### 用户日志
```
[跨子块检测] 选择在同一个子块内 ✅ 检测正确
[跨子块检测] 未检测到跨子块选择,继续单子块处理 ✅ 流程正确
// 但是点击高亮按钮后:
[跨子块操作] 执行操作: highlight-block, 涉及 8 个子块 ❌ 使用了旧数据!
```
---
## 🔍 根源分析
### 问题代码
**文件**: `annotation_logic.js:804-806`
```javascript
const isCrossBlockOperation = annotationContextMenuElement.dataset.contextIsCrossBlock === "true";
if (isCrossBlockOperation) {
return handleCrossBlockMenuAction(action, color, event);
}
```
### Bug 机制
#### 跨子块操作时第1次
```javascript
// annotation_logic.js:1694-1699
annotationContextMenuElement.dataset.contextIsCrossBlock = "true";
annotationContextMenuElement.dataset.contextAffectedSubBlocks = JSON.stringify([...8个子块ID]);
```
✅ 正确设置
#### 单子块操作时第2次
```javascript
// annotation_logic.js:716-738 (修复前)
annotationContextMenuElement.dataset.contextContentIdentifier = ...;
annotationContextMenuElement.dataset.contextTargetIdentifier = ...;
// ... 设置很多属性
// ❌ 问题:没有清除跨子块相关属性!
// contextIsCrossBlock 仍然是 "true"
// contextAffectedSubBlocks 仍然是旧的 8 个子块
```
#### 点击菜单时
```javascript
// annotation_logic.js:804
const isCrossBlockOperation = dataset.contextIsCrossBlock === "true"; // ❌ 读到旧值 "true"
if (isCrossBlockOperation) {
return handleCrossBlockMenuAction(...); // ❌ 误调用跨子块处理
// 使用了旧的 contextAffectedSubBlocks8个子块
}
```
### 时序图
```
时间线:
┌─────────────────────────────────────────────────┐
│ 第 1 次操作跨子块高亮8 个子块) │
├─────────────────────────────────────────────────┤
│ contextIsCrossBlock = "true" ✅ │
│ contextAffectedSubBlocks = [8个ID] ✅ │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 第 2 次操作单子块高亮109.0 内部分文本) │
├─────────────────────────────────────────────────┤
│ detectCrossBlockSelection() → false ✅ │
│ 设置 contextContentIdentifier ✅ │
│ 设置 contextTargetIdentifier ✅ │
│ ❌ 没有清除 contextIsCrossBlock! │
│ ❌ 没有清除 contextAffectedSubBlocks! │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 用户点击"高亮"按钮 │
├─────────────────────────────────────────────────┤
│ 读取 contextIsCrossBlock = "true" ❌ (旧值) │
│ 调用 handleCrossBlockMenuAction ❌ │
│ 使用 contextAffectedSubBlocks = [8个ID] ❌ (旧值│
│ → 高亮了错误的 8 个子块! │
└─────────────────────────────────────────────────┘
```
---
## ✅ 修复方案
### 修复代码
**文件**: `annotation_logic.js:740-743` (新增)
```javascript
// 🔧 BUG FIX: 清除跨子块相关属性,避免单子块操作时误用旧的跨子块数据
delete annotationContextMenuElement.dataset.contextIsCrossBlock;
delete annotationContextMenuElement.dataset.contextCrossBlockAnnotationId;
delete annotationContextMenuElement.dataset.contextAffectedSubBlocks;
```
### 修复位置
在单子块右键事件处理中(`annotation_logic.js:708-766`),设置完其他属性后,**立即清除**跨子块相关属性。
### 修复后的流程
```
第 1 次操作:跨子块高亮
→ contextIsCrossBlock = "true" ✅
第 2 次操作:单子块高亮
→ 清除 contextIsCrossBlock ✅
→ 清除 contextAffectedSubBlocks ✅
→ 设置 contextTargetIdentifier = "109.0" ✅
点击高亮按钮
→ contextIsCrossBlock === undefined ✅
→ isCrossBlockOperation = false ✅
→ 执行单子块高亮 ✅
```
---
## 🧪 测试验证
### 测试场景
1. **场景 A: 跨子块 → 单子块**
- 先跨 5 个子块高亮
- 再在单个子块内选择文本高亮
- **预期**: 只高亮选中的文本 ✅
2. **场景 B: 单子块 → 跨子块**
- 先单子块高亮
- 再跨多个子块高亮
- **预期**: 正确高亮多个子块 ✅
3. **场景 C: 连续单子块操作**
- 连续在不同段落做单子块高亮
- **预期**: 每次都正确高亮 ✅
### 测试步骤
```bash
# 1. 清除缓存并刷新
Ctrl + Shift + R
# 2. 打开历史详情页
# 3. 先选中多个段落 → 右键 → 高亮
# 4. 再选中单个段落内的部分文本 → 右键 → 高亮
# 5. 观察控制台日志
```
**预期日志** ✅:
```
[跨子块检测] 选择在同一个子块内
[AnnotationLogic] 单子块高亮操作...
(不应该出现 "[跨子块操作] 执行操作"
```
---
## 📊 影响评估
### 严重程度: 🔴 高
**原因**:
- 影响批注核心功能
- 可能导致错误的高亮范围
- 用户体验差(高亮了不该高亮的内容)
### 影响范围
| 功能 | 是否受影响 | 影响程度 |
|------|-----------|---------|
| 跨子块高亮 | ✅ 是 | 🔴 高 |
| 单子块高亮 | ✅ 是 | 🔴 高 |
| 批注添加 | ✅ 是 | 🔴 高 |
| 高亮移除 | ✅ 是 | 🟠 中 |
| 其他功能 | ❌ 否 | - |
### 复现条件
- ✅ 必须先做过跨子块操作
- ✅ 然后做单子块操作
- ✅ 两次操作在同一个页面会话中
**复现率**: 100%(符合条件时)
---
## 🚀 部署建议
### 优先级: 🔴 高
建议**立即部署**,原因:
1. Bug 严重影响批注核心功能
2. 修复简单3 行代码),风险极低
3. 不影响其他功能
### 回归测试清单
```
[ ] 跨子块高亮 → 单子块高亮
[ ] 单子块高亮 → 跨子块高亮
[ ] 连续多次单子块操作
[ ] 跨子块批注添加
[ ] 单子块批注添加
[ ] 高亮移除
[ ] 切换标签后批注恢复
```
---
## 📝 相关 Issue
### 用户报告
> "我选中某个段落里面有公式的情况,就会出错"
**根源不是公式**,而是:
- 用户之前可能选中了包含公式的**多个段落**(跨子块)
- 然后选中单个段落内的文本(单子块)
- 触发了状态污染 bug
**公式只是触发场景之一**,任何跨子块 → 单子块操作都会触发。
---
## 🔮 预防措施
### 代码模式
**原则**: 每次设置上下文菜单的 dataset 时,**清除所有可能的旧状态**
**推荐模式**:
```javascript
// 清除所有状态
function clearContextMenuState() {
const keys = Object.keys(annotationContextMenuElement.dataset);
keys.filter(k => k.startsWith('context')).forEach(k => {
delete annotationContextMenuElement.dataset[k];
});
}
// 设置新状态前先清除
clearContextMenuState();
annotationContextMenuElement.dataset.contextXXX = newValue;
```
### 未来改进
**建议**: 使用状态机管理批注上下文,而不是依赖 dataset
```javascript
class AnnotationContext {
constructor() {
this.reset();
}
reset() {
this.isCrossBlock = false;
this.affectedSubBlocks = [];
this.targetIdentifier = null;
// ...
}
setCrossBlock(data) {
this.reset();
this.isCrossBlock = true;
this.affectedSubBlocks = data.subBlocks;
// ...
}
setSingleBlock(data) {
this.reset(); // 自动清除旧状态
this.isCrossBlock = false;
this.targetIdentifier = data.id;
// ...
}
}
```
---
## ✅ 验收清单
- [x] Bug 根源分析完成
- [x] 修复代码实施完成
- [x] 语法检查通过
- [ ] 功能测试通过
- [ ] 回归测试通过
- [ ] 用户验证通过
- [ ] 部署到生产环境
---
## 📌 总结
**Bug**: 跨子块批注状态污染单子块操作
**根源**: 单子块处理时未清除跨子块相关的 dataset 属性
**修复**: 添加 3 行代码清除旧状态
**影响**: 批注核心功能
**优先级**: 🔴 高,建议立即部署
---
**修复完成!** 🎉
感谢用户的详细反馈,这个 bug 发现得非常及时!