fix(TemplateManage): Ctrl+Z undo and smart-field insertion layout

- Intercept Ctrl+Z/Y keyboard shortcuts in keydown listener and route
to custom undoStack/redoStack to fix undo inconsistency.
- Replace execCommand('insertHTML') with precise Range.insertNode()
in insertSmartField to prevent <span> escaping out of <p> when
preceded by <br>.
This commit is contained in:
2026-04-17 13:39:16 +08:00
parent b822bb1b47
commit b155dd42d6
5 changed files with 339 additions and 1 deletions

View File

@@ -544,3 +544,34 @@ if ((settings.autoInsertDelay || 0) > 0) {
- 在 `contentEditable` 中使用 `document.execCommand('insertHTML', ...)` 插入 HTML 时,**传入的字符串必须是无多余空白的紧凑单行**,否则浏览器会将其中的换行符解析为额外的文本节点,破坏排版和光标行为。
- 对于 `contenteditable="false"` 的内联控件,若放置在块级边界(如 `<p>` 开头/结尾),务必增加键盘事件拦截,防止浏览器默认行为误删父级块。
- 默认模板或任何通过代码生成的 HTML应避免为了代码可读性而牺牲运行时 DOM 的纯净性;必要时在生成后对字符串进行 `.replace(/\s+/g, ' ').trim()` 处理。
---
## 记录 21TemplateManage 快捷键 Undo/Redo 与字段插入排版修复
**A. 具体问题**
1. TemplateManage 中删除 smart-field-wrapper 后按键盘 Ctrl+Z 无法撤销,但点击工具栏撤销按钮可以恢复。
2. 当目标段落以 <br> 结尾时,从字段库插入 smart-field-wrapper 会被拆到下一行(<span> 跑到了 <p> 外部)。
**B. 问题产生原因**
1. keydown 事件监听器只拦截了 Backspace/Delete未拦截 Ctrl+Z/Ctrl+Y导致浏览器原生 undo 与自定义 undoStack/
edoStack 完全脱节。
2. insertSmartField 使用 document.execCommand('insertHTML')WebKit/Blink 在块级元素末尾存在 <br> 时,会自动将插入的 inline <span> 修正到块级元素外部,造成排版错位。
**C. 解决问题方法**
1. **快捷键拦截**:在 keydown 监听的最开头增加 Ctrl+Z / Cmd+Z / Ctrl+Shift+Z / Ctrl+Y 的拦截,调用 e.preventDefault() 后路由到 handleUndo() 或 handleRedo()。
2. **精确 Range 插入**:将 insertSmartField 的插入方式从 execCommand('insertHTML') 替换为手动 Range.insertNode()
-
estoreSelection() 恢复光标;
-
ange.deleteContents() 清空当前选区;
- 将 HTML 字符串转为 DocumentFragment
-
ange.insertNode(fragment) 精确插入到 Range 位置;
- setStartAfter(lastNode) 把光标移动到插入内容末尾。
**D. 经验与教训总结**
- 在 contentEditable 中实现自定义撤销栈时,必须**同时拦截界面按钮和键盘快捷键**的 undo/redo否则两套历史机制会互相冲突。
- document.execCommand('insertHTML') 对块级元素边界(尤其是 <br> 结尾)的自动修正行为不可控;需要精确插入时,应优先使用 Range.insertNode() 手动操作 DOM。
- 任何对 contentEditable 的 DOM 修改后都应同步保存内容saveTemplateContent确保 localStorage 中的模板数据与编辑器状态一致。