Skip to content

Commit aabf534

Browse files
committed
Added logic for collapsing columns/columnList nodes when entire columns are removed + added more tests
1 parent b96e9e5 commit aabf534

File tree

3 files changed

+664
-40
lines changed

3 files changed

+664
-40
lines changed

packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ResolvedPos, Slice, type Node } from "prosemirror-model";
1+
import { Fragment, ResolvedPos, Slice, type Node } from "prosemirror-model";
22
import { TextSelection, type Transaction } from "prosemirror-state";
33
import { ReplaceAroundStep } from "prosemirror-transform";
44
import type { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js";
@@ -235,6 +235,73 @@ export function removeAndInsertBlocks<
235235
$newPos.pos + $newPos.nodeAfter!.nodeSize,
236236
Slice.empty,
237237
);
238+
} else if (
239+
$pos.node().type.name === "columnList" &&
240+
$pos.node().childCount === 2
241+
) {
242+
// Checks whether removing the entire column would leave only a single
243+
// remaining `column` node in the columnList. In this case, we need to
244+
// collapse the column list.
245+
const column = getBlockInfoFromResolvedPos($pos);
246+
if (column.blockNoteType !== "column") {
247+
throw new Error(
248+
`Block of type ${column.blockNoteType} was found as child of columnList.`,
249+
);
250+
}
251+
const columnList = getParentBlockInfo(tr.doc, column.bnBlock.beforePos);
252+
if (!columnList) {
253+
throw new Error(
254+
`Block of type column was found without a parent columnList.`,
255+
);
256+
}
257+
if (columnList?.blockNoteType !== "columnList") {
258+
throw new Error(
259+
`Block of type ${columnList.blockNoteType} was found as a parent of column.`,
260+
);
261+
}
262+
263+
if ($pos.node().childCount === 1) {
264+
tr.replaceWith(
265+
columnList.bnBlock.beforePos,
266+
columnList.bnBlock.afterPos,
267+
Fragment.empty,
268+
);
269+
}
270+
271+
tr.replaceWith(
272+
columnList.bnBlock.beforePos,
273+
columnList.bnBlock.afterPos,
274+
$pos.index() === 0
275+
? columnList.bnBlock.node.lastChild!.content
276+
: columnList.bnBlock.node.firstChild!.content,
277+
);
278+
} else if (
279+
node.type.name === "column" &&
280+
node.attrs.id !== $pos.nodeAfter?.attrs.id
281+
) {
282+
// This is a hacky work around to handle an edge case with the previous
283+
// `if else` block. When each `column` of a `columnList` is in the
284+
// `blocksToRemove` array, this is what happens once all but the last 2
285+
// columns are removed:
286+
//
287+
// 1. The second-to-last `column` is removed.
288+
// 2. The last `column` and wrapping `columnList` are collapsed.
289+
// 3. `removedSize` increases by the size of the removed column, and more
290+
// due to positions at the starts/ends of the last `column` and wrapping
291+
// `columnList` also getting removed.
292+
// 3. `tr.doc.descendants` traverses to the last `column`.
293+
// 4. `removedSize` now includes positions that were removed after the
294+
// last `column`. In order for `pos - removedSize` to correctly point to
295+
// the start of the nodes that were previously wrapped by the last
296+
// `column`, `removedPos` must only include positions removed before it.
297+
// 5. The deletion is offset by 3, because of those removed positions
298+
// included in `removedSize` that occur after the last `column`.
299+
//
300+
// Hence why we have to shift the start of the deletion range back by 3.
301+
// The offset for the end of the range is smaller as `node.nodeSize` is
302+
// the size of the whole second `column`, whereas now we are left with
303+
// just its children since it's collapsed - a difference of 2 positions.
304+
tr.delete(pos - removedSize + 3, pos - removedSize + node.nodeSize + 1);
238305
} else if (
239306
$pos.node().type.name === "blockGroup" &&
240307
$pos.node($pos.depth - 1).type.name !== "doc" &&

0 commit comments

Comments
 (0)