Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
import { BlockIdentifier } from "../../../../schema/index.js";
import { getNearestBlockPos } from "../../../getBlockInfoFromPos.js";
import { getNodeById } from "../../../nodeUtil.js";
import { insertBlocks } from "../insertBlocks/insertBlocks.js";
import { removeAndInsertBlocks } from "../replaceBlocks/replaceBlocks.js";
import { fixColumnList } from "../replaceBlocks/util/fixColumnList.js";

type BlockSelectionData = (
| {
Expand Down Expand Up @@ -148,7 +151,7 @@ export function moveBlocks(
referenceBlock: BlockIdentifier,
placement: "before" | "after",
) {
editor.transact(() => {
editor.transact((tr) => {
// A `columnList` reference can be dissolved by `fixColumnList` when its
// `column`s are removed, leaving its ID invalid for re-insertion. Anchor
// to an adjacent block instead, which is unaffected by the removal.
Expand All @@ -164,8 +167,25 @@ export function moveBlocks(
}
}

editor.removeBlocks(blocks);
editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement);
// Don't fix columns/columnLists in the removal step. Otherwise, the
// following case breaks:
// <column>
// <paragraph></paragraph>
// <paragraph>Paragraph</paragraph>
// </column>
// When the non-empty block is moved up, the column is now seen as empty
// and collapsed in the removal step, so the following insertion fails.
const { affectedColumnLists } = removeAndInsertBlocks(tr, blocks, [], {
fixColumns: false,
});
insertBlocks(tr, flattenColumns(blocks), referenceBlock, placement);

affectedColumnLists.forEach((id) => {
const posInfo = getNodeById(id, tr.doc);
if (posInfo) {
fixColumnList(tr, posInfo.posBeforeNode);
}
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ export function removeAndInsertBlocks<
tr: Transaction,
blocksToRemove: BlockIdentifier[],
blocksToInsert: PartialBlock<BSchema, I, S>[],
options: {
fixColumns?: boolean;
} = {},
): {
insertedBlocks: Block<BSchema, I, S>[];
removedBlocks: Block<BSchema, I, S>[];
affectedColumnLists: string[];
} {
const pmSchema = getPmSchema(tr);
// Converts the `PartialBlock`s to ProseMirror nodes to insert them into the
Expand Down Expand Up @@ -112,12 +116,25 @@ export function removeAndInsertBlocks<
);
}

columnListPositions.forEach((pos) => fixColumnList(tr, pos));
// Saves IDs of columnLists containing removed blocks. If `fixColumns` is
// explicitly false, these are needed to run `fixColumnList` manually later.
const affectedColumnLists: string[] = [];
columnListPositions.forEach((pos) => {
const columnList = tr.doc.resolve(pos).nodeAfter;
if (columnList?.type.name === "columnList") {
affectedColumnLists.push(columnList.attrs.id);
}
});

// Collapses empty columns/columnLists
if (options.fixColumns !== false) {
columnListPositions.forEach((pos) => fixColumnList(tr, pos));
}

// Converts the nodes created from `blocksToInsert` into full `Block`s.
const insertedBlocks = nodesToInsert.map((node) =>
nodeToBlock(node, pmSchema),
) as Block<BSchema, I, S>[];

return { insertedBlocks, removedBlocks };
return { insertedBlocks, removedBlocks, affectedColumnLists };
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,183 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Move past empty sibling within a column > Move down below empty sibling 1`] = `
[
{
"children": [
{
"children": [
{
"children": [],
"content": [
{
"styles": {},
"text": "Text 0",
"type": "text",
},
],
"id": "text-0",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
{
"children": [],
"content": [],
"id": "empty-0",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
],
"content": undefined,
"id": "column-empty-0",
"props": {
"width": 1,
},
"type": "column",
},
{
"children": [
{
"children": [],
"content": [],
"id": "empty-1",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
{
"children": [],
"content": [
{
"styles": {},
"text": "Text 1",
"type": "text",
},
],
"id": "text-1",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
],
"content": undefined,
"id": "column-empty-1",
"props": {
"width": 1,
},
"type": "column",
},
],
"content": undefined,
"id": "column-list-empty",
"props": {},
"type": "columnList",
},
]
`;

exports[`Move past empty sibling within a column > Move up above empty sibling 1`] = `
[
{
"children": [
{
"children": [
{
"children": [],
"content": [
{
"styles": {},
"text": "Text 0",
"type": "text",
},
],
"id": "text-0",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
{
"children": [],
"content": [],
"id": "empty-0",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
],
"content": undefined,
"id": "column-empty-0",
"props": {
"width": 1,
},
"type": "column",
},
{
"children": [
{
"children": [],
"content": [],
"id": "empty-1",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
{
"children": [],
"content": [
{
"styles": {},
"text": "Text 1",
"type": "text",
},
],
"id": "text-1",
"props": {
"backgroundColor": "default",
"textAlignment": "left",
"textColor": "default",
},
"type": "paragraph",
},
],
"content": undefined,
"id": "column-empty-1",
"props": {
"width": 1,
},
"type": "column",
},
],
"content": undefined,
"id": "column-list-empty",
"props": {},
"type": "columnList",
},
]
`;

exports[`Test moveBlocksDown > Move into column list 1`] = `
[
{
Expand Down
47 changes: 46 additions & 1 deletion packages/xl-multi-column/src/test/commands/moveBlocks.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vite-plus/test";
import { beforeEach, describe, expect, it } from "vite-plus/test";

import { setupTestEnv } from "../setupTestEnv.js";

Expand Down Expand Up @@ -151,3 +151,48 @@ describe("Test moveBlocksDown", () => {
expect(getEditor().document).toMatchSnapshot();
});
});

describe("Move past empty sibling within a column", () => {
beforeEach(() => {
getEditor().replaceBlocks(getEditor().document, [
{
id: "column-list-empty",
type: "columnList",
children: [
{
id: "column-empty-0",
type: "column",
children: [
{ id: "empty-0", type: "paragraph" },
{ id: "text-0", type: "paragraph", content: "Text 0" },
],
},
{
id: "column-empty-1",
type: "column",
children: [
{ id: "empty-1", type: "paragraph" },
{ id: "text-1", type: "paragraph", content: "Text 1" },
],
},
],
},
]);
});

it("Move up above empty sibling", () => {
getEditor().setTextCursorPosition("text-0");

expect(() => getEditor().moveBlocksUp()).not.toThrow();

expect(getEditor().document).toMatchSnapshot();
});

it("Move down below empty sibling", () => {
getEditor().setTextCursorPosition("empty-0");

expect(() => getEditor().moveBlocksDown()).not.toThrow();

expect(getEditor().document).toMatchSnapshot();
});
});
Loading