fix(editor): appendTransaction 加重入防护 + Enter 行为测试
用户反馈:升级成 taskList 后,空项按 Enter 未退出列表(与斜杠命令创建的 行为不一致)。 单测用 splitListItem 命令验证:升级后的 taskList,有内容项 Enter 分裂出空 taskItem,空项 Enter 正确退出列表(doc 末尾产生 paragraph)。行为与斜杠 命令创建的一致。说明 appendTransaction 产生的节点结构合法。 追加防护:给 appendTransaction 返回的 transaction 打 pluginKey 标记, 重入时跳过,避免 Enter 等操作的文档变化被本插件二次干扰。 happy-dom 无法可靠模拟真实 keydown 链路(派发 KeyboardEvent 时光标映射 不准),Enter 的 bug 需真实浏览器复现。
This commit is contained in:
parent
873ccb1a80
commit
53b53d3169
@ -128,4 +128,31 @@ describe('TaskInputRule (appendTransaction 升级方案)', () => {
|
||||
const block = firstBlock(editor)
|
||||
expect(block.type).toBe('taskList')
|
||||
})
|
||||
|
||||
it('升级后 Enter 行为与斜杠命令创建的一致', async () => {
|
||||
// 场景:升级成含内容的 taskList,Enter 新建空项,空项再 Enter 退出。
|
||||
// 用 splitListItem 命令直接模拟(TaskItem 的 Enter 快捷键即绑定此命令)。
|
||||
typeText(editor, '- ')
|
||||
await flush()
|
||||
typeText(editor, '[ ] 未完成')
|
||||
await flush()
|
||||
|
||||
const taskList = firstBlock(editor)
|
||||
expect(taskList.type).toBe('taskList')
|
||||
expect(taskList.content?.[0]?.attrs?.checked).toBe(false)
|
||||
|
||||
// 第一次 Enter(splitListItem):有内容项 → 分裂出新的空 taskItem
|
||||
editor.commands.splitListItem('taskItem')
|
||||
const after1 = firstBlock(editor)
|
||||
expect(after1.content?.length).toBe(2)
|
||||
expect(after1.content?.[1]?.attrs?.checked).toBe(false)
|
||||
|
||||
// 第二次 Enter(splitListItem):空 taskItem(最后一项,段落为空)→ 应退出列表
|
||||
editor.commands.splitListItem('taskItem')
|
||||
|
||||
// 退出后:taskList 保留原项,文档末尾新增普通段落
|
||||
const json = editor.getJSON()
|
||||
const lastType = json.content?.[json.content.length - 1]?.type
|
||||
expect(lastType).toBe('paragraph')
|
||||
})
|
||||
})
|
||||
|
||||
@ -44,6 +44,11 @@ export const TaskInputRule = Extension.create({
|
||||
new Plugin({
|
||||
key: pluginKey,
|
||||
appendTransaction: (transactions, _oldState, newState) => {
|
||||
// 跳过由本插件产生的 transaction,避免重入(Enter 等操作产生的文档变化
|
||||
// 不应再次触发升级判断,否则可能覆盖 splitListItem 的结果)。
|
||||
const ownTr = transactions.some((tr) => tr.getMeta(pluginKey))
|
||||
if (ownTr) return null
|
||||
|
||||
// 仅在文档实际变化时检查;非文档变化(选区移动)直接跳过。
|
||||
const docChanged = transactions.some((tr) => tr.docChanged)
|
||||
if (!docChanged) return null
|
||||
@ -124,6 +129,8 @@ export const TaskInputRule = Extension.create({
|
||||
const paragraphTextLen = paragraph ? paragraph.content.size : 0
|
||||
cursorPos += 1 + paragraphTextLen
|
||||
replaceTr.setSelection(TextSelection.near(replaceTr.doc.resolve(cursorPos)))
|
||||
// 标记为本插件产生的 transaction,防止 appendTransaction 重入。
|
||||
replaceTr.setMeta(pluginKey, { converted: true })
|
||||
|
||||
return replaceTr
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user