Skip to content

Commit

Permalink
Make Transform.join able to substitute line breaks
Browse files Browse the repository at this point in the history
FIX: Make `Transform.join` convert between newlines and line break replacement nodes
when necessary.

Closes ProseMirror/prosemirror#1490
  • Loading branch information
marijnh committed Oct 10, 2024
1 parent b2e3d40 commit eda8753
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 14 deletions.
41 changes: 37 additions & 4 deletions src/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function setBlockType(tr: Transform, from: number, to: number,
let mapping = tr.mapping.slice(mapFrom)
let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1)
tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1,
new Slice(Fragment.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true))
new Slice(Fragment.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true))
if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom)
return false
}
Expand Down Expand Up @@ -226,8 +226,22 @@ export function canJoin(doc: Node, pos: number): boolean {
$pos.parent.canReplace(index, index + 1)
}

function canAppendWithSubstitutedLinebeaks(a: Node, b: Node) {
if (!b.content.size) a.type.compatibleContent(b.type)
let match: ContentMatch | null = a.contentMatchAt(a.childCount)
let {linebreakReplacement} = a.type.schema
for (let i = 0; i < b.childCount; i++) {
let child = b.child(i)
let type = child.type == linebreakReplacement ? a.type.schema.nodes.text : child.type
match = match.matchType(type)
if (!match) return false
if (!a.type.allowsMarks(child.marks)) return false
}
return match.validEnd
}

function joinable(a: Node | null, b: Node | null) {
return !!(a && b && !a.isLeaf && a.canAppend(b))
return !!(a && b && !a.isLeaf && canAppendWithSubstitutedLinebeaks(a, b))
}

/// Find an ancestor of the given position that can be joined to the
Expand Down Expand Up @@ -256,8 +270,27 @@ export function joinPoint(doc: Node, pos: number, dir = -1) {
}

export function join(tr: Transform, pos: number, depth: number) {
let step = new ReplaceStep(pos - depth, pos + depth, Slice.empty, true)
tr.step(step)
let convertNewlines = null
let {linebreakReplacement} = tr.doc.type.schema
if (linebreakReplacement) {
let before = tr.doc.resolve(pos - depth).node().type
let pre = before.whitespace == "pre"
let supportLinebreak = !!before.contentMatch.matchType(linebreakReplacement)
if (pre && !supportLinebreak) convertNewlines = false
else if (!pre && supportLinebreak) convertNewlines = true
}
let mapFrom = tr.steps.length
if (convertNewlines === false) {
let $after = tr.doc.resolve(pos + depth)
replaceLinebreaks(tr, $after.node(), $after.before(), mapFrom)
}
let mapping = tr.mapping.slice(mapFrom), start = mapping.map(pos - depth)
tr.step(new ReplaceStep(start, mapping.map(pos + depth, - 1), Slice.empty, true))
if (convertNewlines === true) {
let $full = tr.doc.resolve(start)
replaceNewlines(tr, $full.node(), $full.before(), tr.steps.length)
}
return tr
}

/// Try to find a point where a node of the given type can be inserted
Expand Down
28 changes: 18 additions & 10 deletions test/test-trans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ describe("Transform", () => {
doc(pre("fo"), p(em("ar")))))
})

const linebreakSchema = new Schema({
nodes: schema.spec.nodes.update("hard_break", {...schema.spec.nodes.get("hard_break"), linebreakReplacement: true})
})
const lb = builders(linebreakSchema, {
p: {nodeType: "paragraph"},
pre: {nodeType: "code_block"},
br: {nodeType: "hard_break"},
h1: {nodeType: "heading", level: 1},
})

describe("join", () => {
function join(doc: Node, expect: Node) {
testTransform(new Transform(doc).join(tag(doc, "a")), expect)
Expand Down Expand Up @@ -221,6 +231,14 @@ describe("Transform", () => {
it("can join textblocks", () =>
join(doc(p("foo"), "<a>", p("bar")),
doc(p("foo<a>bar"))))

it("converts newlines to line breaks", () =>
join(lb.doc(lb.p("one"), "<a>", lb.pre("two\nthree")),
lb.doc(lb.p("one<a>two", lb.br(), "three"))))

it("converts line breaks to newlines", () =>
join(lb.doc(lb.pre("one"), "<a>", lb.p("two", lb.br(), "three")),
lb.doc(lb.pre("one<a>two\nthree"))))
})

describe("split", () => {
Expand Down Expand Up @@ -408,16 +426,6 @@ describe("Transform", () => {
doc(pre("<a>hello"), pre("okay"), ul(li(p("foo<b>")))),
"code_block"))

const linebreakSchema = new Schema({
nodes: schema.spec.nodes.update("hard_break", {...schema.spec.nodes.get("hard_break"), linebreakReplacement: true})
})
const lb = builders(linebreakSchema, {
p: {nodeType: "paragraph"},
pre: {nodeType: "code_block"},
br: {nodeType: "hard_break"},
h1: {nodeType: "heading", level: 1},
})

it("converts newlines to linebreak replacements when appropriate", () => {
type(lb.doc(lb.pre("<a>one\ntwo\nthree")),
lb.doc(lb.p("<a>one", lb.br(), "two", lb.br(), "three")),
Expand Down

0 comments on commit eda8753

Please sign in to comment.