Skip to content

Commit

Permalink
Make splitBlock capable of splitting inline nodes
Browse files Browse the repository at this point in the history
FIX: Make `splitBlock` smart enough to split blocks when the cursor is inside
a nested inline node.

See https://discuss.prosemirror.net/t/inline-nodes-are-not-split-when-enter-is-pressed-between-the-node/7824
  • Loading branch information
marijnh committed Oct 23, 2024
1 parent 9b8d861 commit 8cbb694
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 15 deletions.
43 changes: 28 additions & 15 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,25 +365,38 @@ export function splitBlockAs(
return true
}

if (!$from.parent.isBlock) return false
if (!$from.depth) return false
let types: (null | {type: NodeType, attrs?: Attrs | null})[] = []
let splitDepth, deflt, atEnd = false, atStart = false
for (let d = $from.depth;; d--) {
let node = $from.node(d)
if (node.isBlock) {
atEnd = $from.end(d) == $from.pos + ($from.depth - d)
atStart = $from.start(d) == $from.pos - ($from.depth - d)
deflt = defaultBlockAt($from.node(d - 1).contentMatchAt($from.indexAfter(d - 1)))
let splitType = splitNode && splitNode($to.parent, atEnd, $from)
types.unshift(splitType || (atEnd && deflt ? {type: deflt} : null))
splitDepth = d
break
} else {
if (d == 1) return false
types.unshift(null)
}
}

let atEnd = $to.parentOffset == $to.parent.content.size
let tr = state.tr
if (state.selection instanceof TextSelection || state.selection instanceof AllSelection) tr.deleteSelection()
let deflt = $from.depth == 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
let splitType = splitNode && splitNode($to.parent, atEnd, $from)
let types = splitType ? [splitType] : atEnd && deflt ? [{type: deflt}] : undefined
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{type: deflt}] : undefined)) {
if (deflt) types = [{type: deflt}]
can = true
let splitPos = tr.mapping.map($from.pos)
let can = canSplit(tr.doc, splitPos, types.length, types)
if (!can) {
types[0] = deflt ? {type: deflt} : null
can = canSplit(tr.doc, splitPos, types.length, types)
}
if (!can) return false
tr.split(tr.mapping.map($from.pos), 1, types)
if (!atEnd && !$from.parentOffset && $from.parent.type != deflt) {
let first = tr.mapping.map($from.before()), $first = tr.doc.resolve(first)
if (deflt && $from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt))
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt)
tr.split(splitPos, types.length, types)
if (!atEnd && atStart && $from.node(splitDepth).type != deflt) {
let first = tr.mapping.map($from.before(splitDepth)), $first = tr.doc.resolve(first)
if (deflt && $from.node(splitDepth - 1).canReplaceWith($first.index(), $first.index() + 1, deflt))
tr.setNodeMarkup(tr.mapping.map($from.before(splitDepth)), deflt)
}
if (dispatch) dispatch(tr.scrollIntoView())
return true
Expand Down
15 changes: 15 additions & 0 deletions test/test-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ describe("splitBlock", () => {
content: "inline*"
}).update("doc", {
content: "heading block*"
}).addToEnd("span", {
inline: true,
group: "inline",
content: "inline*"
})
})
function hDoc(a: number) {
Expand All @@ -384,6 +388,17 @@ describe("splitBlock", () => {
hSchema.node("paragraph", null, hSchema.text("foobar"))
])))

it("can split an inline node", () => {
let d = hSchema.node("doc", null, [
hSchema.node("heading", {level: 1}, [
hSchema.node("span", null, hSchema.text("abcd"))])])
;(d as any).tag = {a: 4}
apply(d, splitBlock, hSchema.node("doc", null, [
hSchema.node("heading", {level: 1}, hSchema.node("span", null, hSchema.text("ab"))),
hSchema.node("paragraph", null, hSchema.node("span", null, hSchema.text("cd")))
]))
})

it("prefers textblocks", () => {
let s = new Schema({nodes: {
text: {},
Expand Down

0 comments on commit 8cbb694

Please sign in to comment.