Skip to content

Commit 218f22a

Browse files
committed
Implemented PR feedback
1 parent 38d2f74 commit 218f22a

File tree

6 files changed

+870
-168
lines changed

6 files changed

+870
-168
lines changed

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

Lines changed: 2 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Slice, type Node } from "prosemirror-model";
1+
import { type Node } from "prosemirror-model";
22
import { type Transaction } from "prosemirror-state";
3-
import { ReplaceAroundStep } from "prosemirror-transform";
43
import type { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js";
54
import type {
65
BlockIdentifier,
@@ -11,161 +10,7 @@ import type {
1110
import { blockToNode } from "../../../nodeConversions/blockToNode.js";
1211
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";
1312
import { getPmSchema } from "../../../pmUtil.js";
14-
15-
/**
16-
* Checks if a `column` node is empty, i.e. if it has only a single empty
17-
* block.
18-
* @param column The column to check.
19-
* @returns Whether the column is empty.
20-
*/
21-
function isEmptyColumn(column: Node) {
22-
if (!column || column.type.name !== "column") {
23-
throw new Error("Invalid columnPos: does not point to column node.");
24-
}
25-
26-
const blockContainer = column.firstChild;
27-
if (!blockContainer) {
28-
throw new Error("Invalid column: does not have child node.");
29-
}
30-
31-
const blockContent = blockContainer.firstChild;
32-
if (!blockContent) {
33-
throw new Error("Invalid blockContainer: does not have child node.");
34-
}
35-
36-
return (
37-
column.childCount === 1 &&
38-
blockContainer.childCount === 1 &&
39-
blockContent.type.spec.content === "inline*" &&
40-
blockContent.content.content.length === 0
41-
);
42-
}
43-
44-
/**
45-
* Removes all empty `column` nodes in a `columnList`. A `column` node is empty
46-
* if it has only a single empty block. If, however, removing the `column`s
47-
* leaves the `columnList` that has fewer than two, ProseMirror will re-add
48-
* empty columns.
49-
* @param tr The `Transaction` to add the changes to.
50-
* @param columnListPos The position just before the `columnList` node.
51-
*/
52-
function removeEmptyColumns(tr: Transaction, columnListPos: number) {
53-
const $columnListPos = tr.doc.resolve(columnListPos);
54-
const columnList = $columnListPos.nodeAfter;
55-
if (!columnList || columnList.type.name !== "columnList") {
56-
throw new Error(
57-
"Invalid columnListPos: does not point to columnList node.",
58-
);
59-
}
60-
61-
for (
62-
let columnIndex = columnList.childCount - 1;
63-
columnIndex >= 0;
64-
columnIndex--
65-
) {
66-
const columnPos = tr.doc
67-
.resolve($columnListPos.pos + 1)
68-
.posAtIndex(columnIndex);
69-
const $columnPos = tr.doc.resolve(columnPos);
70-
const column = $columnPos.nodeAfter;
71-
if (!column || column.type.name !== "column") {
72-
throw new Error("Invalid columnPos: does not point to column node.");
73-
}
74-
75-
if (isEmptyColumn(column)) {
76-
tr.delete(columnPos, columnPos + column?.nodeSize);
77-
}
78-
}
79-
}
80-
81-
/**
82-
* Fixes potential issues in a `columnList` node after a
83-
* `blockContainer`/`column` node is (re)moved from it:
84-
*
85-
* - Removes all empty `column` nodes. A `column` node is empty if it has only
86-
* a single empty block.
87-
* - If all but one `column` nodes are empty, replaces the `columnList` with
88-
* the content of the non-empty `column`.
89-
* - If all `column` nodes are empty, removes the `columnList` entirely.
90-
* @param tr The `Transaction` to add the changes to.
91-
* @param columnListPos
92-
* @returns The position just before the `columnList` node.
93-
*/
94-
export function fixColumnList(tr: Transaction, columnListPos: number) {
95-
removeEmptyColumns(tr, columnListPos);
96-
97-
const $columnListPos = tr.doc.resolve(columnListPos);
98-
const columnList = $columnListPos.nodeAfter;
99-
if (!columnList || columnList.type.name !== "columnList") {
100-
throw new Error(
101-
"Invalid columnListPos: does not point to columnList node.",
102-
);
103-
}
104-
105-
if (columnList.childCount > 2) {
106-
return;
107-
}
108-
109-
const firstColumnBeforePos = columnListPos + 1;
110-
const $firstColumnBeforePos = tr.doc.resolve(firstColumnBeforePos);
111-
const firstColumn = $firstColumnBeforePos.nodeAfter;
112-
113-
const lastColumnAfterPos = columnListPos + columnList.nodeSize - 1;
114-
const $lastColumnAfterPos = tr.doc.resolve(lastColumnAfterPos);
115-
const lastColumn = $lastColumnAfterPos.nodeBefore;
116-
117-
if (!firstColumn || !lastColumn) {
118-
throw new Error("Invalid columnList: does not have child node.");
119-
}
120-
121-
const firstColumnEmpty = isEmptyColumn(firstColumn);
122-
const lastColumnEmpty = isEmptyColumn(lastColumn);
123-
124-
if (firstColumnEmpty && lastColumnEmpty) {
125-
// Removes `columnList`
126-
tr.delete(columnListPos, columnListPos + columnList.nodeSize);
127-
128-
return;
129-
}
130-
131-
if (firstColumnEmpty) {
132-
tr.step(
133-
new ReplaceAroundStep(
134-
// Replaces `columnList`.
135-
columnListPos,
136-
columnListPos + columnList.nodeSize,
137-
// Replaces with content of last `column`.
138-
lastColumnAfterPos - lastColumn.nodeSize + 1,
139-
lastColumnAfterPos - 1,
140-
// Doesn't append anything.
141-
Slice.empty,
142-
0,
143-
false,
144-
),
145-
);
146-
147-
return;
148-
}
149-
150-
if (lastColumnEmpty) {
151-
tr.step(
152-
new ReplaceAroundStep(
153-
// Replaces `columnList`.
154-
columnListPos,
155-
columnListPos + columnList.nodeSize,
156-
// Replaces with content of first `column`.
157-
firstColumnBeforePos + 1,
158-
firstColumnBeforePos + firstColumn.nodeSize - 1,
159-
// Doesn't append anything.
160-
Slice.empty,
161-
0,
162-
false,
163-
),
164-
);
165-
166-
return;
167-
}
168-
}
13+
import { fixColumnList } from "./util/fixColumnList.js";
16914

17015
export function removeAndInsertBlocks<
17116
BSchema extends BlockSchema,
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { Slice, type Node } from "prosemirror-model";
2+
import { type Transaction } from "prosemirror-state";
3+
import { ReplaceAroundStep } from "prosemirror-transform";
4+
5+
/**
6+
* Checks if a `column` node is empty, i.e. if it has only a single empty
7+
* block.
8+
* @param column The column to check.
9+
* @returns Whether the column is empty.
10+
*/
11+
export function isEmptyColumn(column: Node) {
12+
if (!column || column.type.name !== "column") {
13+
throw new Error("Invalid columnPos: does not point to column node.");
14+
}
15+
16+
const blockContainer = column.firstChild;
17+
if (!blockContainer) {
18+
throw new Error("Invalid column: does not have child node.");
19+
}
20+
21+
const blockContent = blockContainer.firstChild;
22+
if (!blockContent) {
23+
throw new Error("Invalid blockContainer: does not have child node.");
24+
}
25+
26+
return (
27+
column.childCount === 1 &&
28+
blockContainer.childCount === 1 &&
29+
blockContent.type.spec.content === "inline*" &&
30+
blockContent.content.content.length === 0
31+
);
32+
}
33+
34+
/**
35+
* Removes all empty `column` nodes in a `columnList`. A `column` node is empty
36+
* if it has only a single empty block. If, however, removing the `column`s
37+
* leaves the `columnList` that has fewer than two, ProseMirror will re-add
38+
* empty columns.
39+
* @param tr The `Transaction` to add the changes to.
40+
* @param columnListPos The position just before the `columnList` node.
41+
*/
42+
export function removeEmptyColumns(tr: Transaction, columnListPos: number) {
43+
const $columnListPos = tr.doc.resolve(columnListPos);
44+
const columnList = $columnListPos.nodeAfter;
45+
if (!columnList || columnList.type.name !== "columnList") {
46+
throw new Error(
47+
"Invalid columnListPos: does not point to columnList node.",
48+
);
49+
}
50+
51+
for (
52+
let columnIndex = columnList.childCount - 1;
53+
columnIndex >= 0;
54+
columnIndex--
55+
) {
56+
const columnPos = tr.doc
57+
.resolve($columnListPos.pos + 1)
58+
.posAtIndex(columnIndex);
59+
const $columnPos = tr.doc.resolve(columnPos);
60+
const column = $columnPos.nodeAfter;
61+
if (!column || column.type.name !== "column") {
62+
throw new Error("Invalid columnPos: does not point to column node.");
63+
}
64+
65+
if (isEmptyColumn(column)) {
66+
tr.delete(columnPos, columnPos + column?.nodeSize);
67+
}
68+
}
69+
}
70+
71+
/**
72+
* Fixes potential issues in a `columnList` node after a
73+
* `blockContainer`/`column` node is (re)moved from it:
74+
*
75+
* - Removes all empty `column` nodes. A `column` node is empty if it has only
76+
* a single empty block.
77+
* - If all but one `column` nodes are empty, replaces the `columnList` with
78+
* the content of the non-empty `column`.
79+
* - If all `column` nodes are empty, removes the `columnList` entirely.
80+
* @param tr The `Transaction` to add the changes to.
81+
* @param columnListPos
82+
* @returns The position just before the `columnList` node.
83+
*/
84+
export function fixColumnList(tr: Transaction, columnListPos: number) {
85+
removeEmptyColumns(tr, columnListPos);
86+
87+
const $columnListPos = tr.doc.resolve(columnListPos);
88+
const columnList = $columnListPos.nodeAfter;
89+
if (!columnList || columnList.type.name !== "columnList") {
90+
throw new Error(
91+
"Invalid columnListPos: does not point to columnList node.",
92+
);
93+
}
94+
95+
if (columnList.childCount > 2) {
96+
// Do nothing if the `columnList` has at least two non-empty `column`s.
97+
return;
98+
}
99+
100+
if (columnList.childCount < 2) {
101+
throw new Error("Invalid columnList: contains fewer than two children.");
102+
}
103+
104+
const firstColumnBeforePos = columnListPos + 1;
105+
const $firstColumnBeforePos = tr.doc.resolve(firstColumnBeforePos);
106+
const firstColumn = $firstColumnBeforePos.nodeAfter;
107+
108+
const lastColumnAfterPos = columnListPos + columnList.nodeSize - 1;
109+
const $lastColumnAfterPos = tr.doc.resolve(lastColumnAfterPos);
110+
const lastColumn = $lastColumnAfterPos.nodeBefore;
111+
112+
if (!firstColumn || !lastColumn) {
113+
throw new Error("Invalid columnList: does not contain children.");
114+
}
115+
116+
const firstColumnEmpty = isEmptyColumn(firstColumn);
117+
const lastColumnEmpty = isEmptyColumn(lastColumn);
118+
119+
if (firstColumnEmpty && lastColumnEmpty) {
120+
// Removes `columnList`
121+
tr.delete(columnListPos, columnListPos + columnList.nodeSize);
122+
123+
return;
124+
}
125+
126+
if (firstColumnEmpty) {
127+
tr.step(
128+
new ReplaceAroundStep(
129+
// Replaces `columnList`.
130+
columnListPos,
131+
columnListPos + columnList.nodeSize,
132+
// Replaces with content of last `column`.
133+
lastColumnAfterPos - lastColumn.nodeSize + 1,
134+
lastColumnAfterPos - 1,
135+
// Doesn't append anything.
136+
Slice.empty,
137+
0,
138+
false,
139+
),
140+
);
141+
142+
return;
143+
}
144+
145+
if (lastColumnEmpty) {
146+
tr.step(
147+
new ReplaceAroundStep(
148+
// Replaces `columnList`.
149+
columnListPos,
150+
columnListPos + columnList.nodeSize,
151+
// Replaces with content of first `column`.
152+
firstColumnBeforePos + 1,
153+
firstColumnBeforePos + firstColumn.nodeSize - 1,
154+
// Doesn't append anything.
155+
Slice.empty,
156+
0,
157+
false,
158+
),
159+
);
160+
161+
return;
162+
}
163+
}

0 commit comments

Comments
 (0)