如何阻止ProseMirror中清空title节点后section内容自动上移?
解决ProseMirror中清空title节点后section内容上移的问题
自定义Schema(简化版)
const schema = new Schema({ nodes: { doc: {content: 'title section+'}, title: { content: 'text*', marks: "", isolating: true, toDOM() { return ["span", {class: "document-title"}, 0] }, parseDOM: [{tag: "span.document-title"}] }, section: { group: 'section', content: 'block+', toDOM() {return ['section', 0]} }, paragraph: { content: "inline*", group: "block", parseDOM: [{ tag: "p" }], toDOM() { return ['p', 0]; } }, text: { group: 'inline', inline: true } } })
文档结构
My document title <-- 位于span.document-title内
My document body <-- 位于section内
对应的HTML结构:
<span class="document-title">My document title</span> <section>My document body</section>
问题描述
清空title节点的所有内容后,<section>内的内容会自动上移,导致title节点的内容变成"My document body",这不符合预期。需要title节点作为独立区域,编辑操作不影响section内的内容。
原本以为给title节点设置isolating: true可以实现该效果,因为ProseMirror官方文档说明:
启用后,此类节点的两侧会被视为边界,常规编辑操作(如退格或提升)无法跨越。
但该设置没有起到预期作用。
原因分析
isolating: true确实会阻止退格、提升等编辑操作跨越节点边界,但问题核心是当title节点内容为空时,编辑器的节点合并逻辑被触发。由于title的content定义为text*(允许空内容),编辑器会认为空的title节点可以被“合并”到相邻的section节点中,从而导致内容移位。
解决方案
1. 强制title节点始终保留存在性
修改title节点的定义,添加empty()方法让ProseMirror认为它永远不能被视为空节点,阻止合并逻辑触发:
title: { content: 'text*', marks: "", isolating: true, toDOM() { return ["span", {class: "document-title"}, 0] }, parseDOM: [{tag: "span.document-title"}], // 强制title节点即使无内容也保持存在 empty() { return false; } }
2. 自定义按键拦截规则
通过编辑器插件监听退格操作,当title节点为空时阻止退格,避免内容被拉进title区域:
const preventContentShift = new Plugin({ props: { handleKeyDown(view, event) { const { state } = view; const { $from } = state.selection; // 当在title节点内且内容为空时,拦截退格键 if ($from.parent.type.name === 'title' && $from.parent.textContent === '' && event.key === 'Backspace') { return true; } return false; } } }); // 初始化编辑器时加载插件 new EditorView(document.querySelector('#editor'), { state: EditorState.create({ schema, plugins: [preventContentShift] }) });
3. 将title改为块级DOM元素(可选)
把title的DOM从行内span改为块级元素(如div),减少编辑器对相邻节点的合并倾向:
title: { content: 'text*', marks: "", isolating: true, toDOM() { return ["div", {class: "document-title"}, 0] }, parseDOM: [{tag: "div.document-title"}], empty() { return false; } }
内容的提问来源于stack exchange,提问作者Kinglui11
相关产品推荐
相关产品推荐

