@blocknote/core 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocknote.js +1791 -1483
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +6 -6
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/src/api/blockManipulation/commands/insertBlocks/insertBlocks.js +6 -3
- package/dist/src/api/blockManipulation/commands/insertBlocks/insertBlocks.js.map +1 -1
- package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.js +219 -0
- package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.js.map +1 -0
- package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.js +175 -0
- package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.js.map +1 -0
- package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.test.js +3 -0
- package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.test.js.map +1 -1
- package/dist/src/api/blockManipulation/commands/updateBlock/updateBlock.js +6 -3
- package/dist/src/api/blockManipulation/commands/updateBlock/updateBlock.js.map +1 -1
- package/dist/src/api/blockManipulation/getBlock/getBlock.js +56 -0
- package/dist/src/api/blockManipulation/getBlock/getBlock.js.map +1 -0
- package/dist/src/api/blockManipulation/selections/selection.js +149 -0
- package/dist/src/api/blockManipulation/selections/selection.js.map +1 -0
- package/dist/src/api/blockManipulation/selections/selection.test.js +39 -0
- package/dist/src/api/blockManipulation/selections/selection.test.js.map +1 -0
- package/dist/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.js +3 -0
- package/dist/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.js.map +1 -1
- package/dist/src/api/clipboard/fromClipboard/handleVSCodePaste.js +3 -3
- package/dist/src/api/clipboard/fromClipboard/handleVSCodePaste.js.map +1 -1
- package/dist/src/api/nodeUtil.js +1 -1
- package/dist/src/api/nodeUtil.js.map +1 -1
- package/dist/src/blocks/CodeBlockContent/CodeBlockContent.js +15 -7
- package/dist/src/blocks/CodeBlockContent/CodeBlockContent.js.map +1 -1
- package/dist/src/blocks/CodeBlockContent/defaultSupportedLanguages.js +38 -18
- package/dist/src/blocks/CodeBlockContent/defaultSupportedLanguages.js.map +1 -1
- package/dist/src/blocks/TableBlockContent/TableExtension.js +8 -1
- package/dist/src/blocks/TableBlockContent/TableExtension.js.map +1 -1
- package/dist/src/editor/BlockNoteEditor.js +59 -57
- package/dist/src/editor/BlockNoteEditor.js.map +1 -1
- package/dist/src/editor/BlockNoteExtensions.js +2 -1
- package/dist/src/editor/BlockNoteExtensions.js.map +1 -1
- package/dist/src/extensions/FormattingToolbar/FormattingToolbarPlugin.js +4 -2
- package/dist/src/extensions/FormattingToolbar/FormattingToolbarPlugin.js.map +1 -1
- package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js +10 -8
- package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js.map +1 -1
- package/dist/src/extensions/LinkToolbar/LinkToolbarPlugin.js +7 -3
- package/dist/src/extensions/LinkToolbar/LinkToolbarPlugin.js.map +1 -1
- package/dist/src/extensions/Placeholder/PlaceholderPlugin.js +13 -7
- package/dist/src/extensions/Placeholder/PlaceholderPlugin.js.map +1 -1
- package/dist/src/extensions/SideMenu/SideMenuPlugin.js +5 -1
- package/dist/src/extensions/SideMenu/SideMenuPlugin.js.map +1 -1
- package/dist/src/extensions/SideMenu/dragging.js +5 -1
- package/dist/src/extensions/SideMenu/dragging.js.map +1 -1
- package/dist/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.js +0 -3
- package/dist/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.js.map +1 -1
- package/dist/src/extensions/TableHandles/TableHandlesPlugin.js +25 -8
- package/dist/src/extensions/TableHandles/TableHandlesPlugin.js.map +1 -1
- package/dist/src/i18n/locales/ru.js +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +3 -3
- package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +6 -6
- package/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +9506 -0
- package/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts +295 -0
- package/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts +338 -0
- package/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +4 -0
- package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +11 -3
- package/src/api/blockManipulation/getBlock/getBlock.ts +141 -0
- package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +660 -0
- package/src/api/blockManipulation/selections/selection.test.ts +56 -0
- package/src/api/blockManipulation/selections/selection.ts +244 -0
- package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +4 -0
- package/src/api/clipboard/fromClipboard/handleVSCodePaste.ts +4 -4
- package/src/api/nodeUtil.ts +2 -2
- package/src/blocks/CodeBlockContent/CodeBlockContent.ts +18 -8
- package/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +38 -18
- package/src/blocks/TableBlockContent/TableExtension.ts +12 -1
- package/src/editor/Block.css +3 -0
- package/src/editor/BlockNoteEditor.ts +93 -85
- package/src/editor/BlockNoteExtensions.ts +3 -1
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +4 -2
- package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +11 -8
- package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +11 -4
- package/src/extensions/Placeholder/PlaceholderPlugin.ts +23 -15
- package/src/extensions/SideMenu/SideMenuPlugin.ts +5 -1
- package/src/extensions/SideMenu/dragging.ts +5 -1
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +0 -5
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +34 -9
- package/src/i18n/locales/ru.ts +1 -1
- package/src/index.ts +1 -0
- package/types/src/api/blockManipulation/commands/moveBlocks/moveBlocks.d.ts +15 -0
- package/types/src/api/blockManipulation/getBlock/getBlock.d.ts +7 -0
- package/types/src/api/blockManipulation/selections/selection.d.ts +5 -0
- package/types/src/api/blockManipulation/selections/selection.test.d.ts +1 -0
- package/types/src/api/nodeUtil.d.ts +1 -1
- package/types/src/editor/BlockNoteEditor.d.ts +54 -10
- package/types/src/editor/BlockNoteExtensions.d.ts +1 -0
- package/types/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.d.ts +1 -0
- package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +1 -1
- package/types/src/index.d.ts +1 -0
- package/types/src/pm-nodes/BlockContainer.d.ts +2 -2
- package/types/src/pm-nodes/BlockGroup.d.ts +2 -2
- package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.js +0 -116
- package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.js.map +0 -1
- package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js +0 -110
- package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js.map +0 -1
- package/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap +0 -3799
- package/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +0 -196
- package/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +0 -176
- package/types/src/api/blockManipulation/commands/moveBlock/moveBlock.d.ts +0 -5
- /package/types/src/api/blockManipulation/commands/{moveBlock/moveBlock.test.d.ts → moveBlocks/moveBlocks.test.d.ts} +0 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { NodeSelection, TextSelection } from "prosemirror-state";
|
|
2
|
+
import { CellSelection } from "prosemirror-tables";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js";
|
|
6
|
+
import { setupTestEnv } from "../../setupTestEnv.js";
|
|
7
|
+
import {
|
|
8
|
+
moveBlocksDown,
|
|
9
|
+
moveBlocksUp,
|
|
10
|
+
moveSelectedBlocksAndSelection,
|
|
11
|
+
} from "./moveBlocks.js";
|
|
12
|
+
|
|
13
|
+
const getEditor = setupTestEnv();
|
|
14
|
+
|
|
15
|
+
function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") {
|
|
16
|
+
const blockInfo = getBlockInfoFromSelection(getEditor()._tiptapEditor.state);
|
|
17
|
+
if (!blockInfo.isBlockContainer) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Selection points to a ${blockInfo.blockNoteType} node, not a blockContainer node`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const { blockContent } = blockInfo;
|
|
23
|
+
|
|
24
|
+
if (selectionType === "cell") {
|
|
25
|
+
getEditor()._tiptapEditor.view.dispatch(
|
|
26
|
+
getEditor()._tiptapEditor.state.tr.setSelection(
|
|
27
|
+
CellSelection.create(
|
|
28
|
+
getEditor()._tiptapEditor.state.doc,
|
|
29
|
+
getEditor()
|
|
30
|
+
._tiptapEditor.state.doc.resolve(blockContent.beforePos + 3)
|
|
31
|
+
.before(),
|
|
32
|
+
getEditor()
|
|
33
|
+
._tiptapEditor.state.doc.resolve(blockContent.afterPos - 3)
|
|
34
|
+
.before()
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
} else if (selectionType === "node") {
|
|
39
|
+
getEditor()._tiptapEditor.view.dispatch(
|
|
40
|
+
getEditor()._tiptapEditor.state.tr.setSelection(
|
|
41
|
+
NodeSelection.create(
|
|
42
|
+
getEditor()._tiptapEditor.state.doc,
|
|
43
|
+
blockContent.beforePos
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
getEditor()._tiptapEditor.view.dispatch(
|
|
49
|
+
getEditor()._tiptapEditor.state.tr.setSelection(
|
|
50
|
+
TextSelection.create(
|
|
51
|
+
getEditor()._tiptapEditor.state.doc,
|
|
52
|
+
blockContent.beforePos + 1,
|
|
53
|
+
blockContent.afterPos - 1
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("Test moveSelectedBlockAndSelection", () => {
|
|
61
|
+
it("Text selection", () => {
|
|
62
|
+
getEditor().setTextCursorPosition("paragraph-1");
|
|
63
|
+
makeSelectionSpanContent("text");
|
|
64
|
+
|
|
65
|
+
moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
|
|
66
|
+
|
|
67
|
+
const selection = getEditor()._tiptapEditor.state.selection;
|
|
68
|
+
getEditor().setTextCursorPosition("paragraph-1");
|
|
69
|
+
makeSelectionSpanContent("text");
|
|
70
|
+
|
|
71
|
+
expect(
|
|
72
|
+
selection.eq(getEditor()._tiptapEditor.state.selection)
|
|
73
|
+
).toBeTruthy();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("Node selection", () => {
|
|
77
|
+
getEditor().setTextCursorPosition("image-0");
|
|
78
|
+
makeSelectionSpanContent("node");
|
|
79
|
+
|
|
80
|
+
moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
|
|
81
|
+
|
|
82
|
+
const selection = getEditor()._tiptapEditor.state.selection;
|
|
83
|
+
getEditor().setTextCursorPosition("image-0");
|
|
84
|
+
makeSelectionSpanContent("node");
|
|
85
|
+
|
|
86
|
+
expect(
|
|
87
|
+
selection.eq(getEditor()._tiptapEditor.state.selection)
|
|
88
|
+
).toBeTruthy();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("Cell selection", () => {
|
|
92
|
+
getEditor().setTextCursorPosition("table-0");
|
|
93
|
+
makeSelectionSpanContent("cell");
|
|
94
|
+
|
|
95
|
+
moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
|
|
96
|
+
|
|
97
|
+
const selection = getEditor()._tiptapEditor.state.selection;
|
|
98
|
+
getEditor().setTextCursorPosition("table-0");
|
|
99
|
+
makeSelectionSpanContent("cell");
|
|
100
|
+
|
|
101
|
+
expect(
|
|
102
|
+
selection.eq(getEditor()._tiptapEditor.state.selection)
|
|
103
|
+
).toBeTruthy();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("Multiple block selection", () => {
|
|
107
|
+
getEditor().setSelection("paragraph-1", "paragraph-2");
|
|
108
|
+
|
|
109
|
+
moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
|
|
110
|
+
|
|
111
|
+
const selection = getEditor()._tiptapEditor.state.selection;
|
|
112
|
+
getEditor().setSelection("paragraph-1", "paragraph-2");
|
|
113
|
+
|
|
114
|
+
expect(
|
|
115
|
+
selection.eq(getEditor()._tiptapEditor.state.selection)
|
|
116
|
+
).toBeTruthy();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("Multiple block selection with table", () => {
|
|
120
|
+
getEditor().setSelection("paragraph-6", "table-0");
|
|
121
|
+
|
|
122
|
+
moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
|
|
123
|
+
|
|
124
|
+
const selection = getEditor()._tiptapEditor.state.selection;
|
|
125
|
+
getEditor().setSelection("paragraph-6", "table-0");
|
|
126
|
+
|
|
127
|
+
expect(
|
|
128
|
+
selection.eq(getEditor()._tiptapEditor.state.selection)
|
|
129
|
+
).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("Test moveBlocksUp", () => {
|
|
134
|
+
it("Basic", () => {
|
|
135
|
+
getEditor().setTextCursorPosition("paragraph-1");
|
|
136
|
+
|
|
137
|
+
moveBlocksUp(getEditor());
|
|
138
|
+
|
|
139
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("Into children", () => {
|
|
143
|
+
getEditor().setTextCursorPosition("paragraph-2");
|
|
144
|
+
|
|
145
|
+
moveBlocksUp(getEditor());
|
|
146
|
+
|
|
147
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("Out of children", () => {
|
|
151
|
+
getEditor().setTextCursorPosition("nested-paragraph-1");
|
|
152
|
+
|
|
153
|
+
moveBlocksUp(getEditor());
|
|
154
|
+
|
|
155
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("First block", () => {
|
|
159
|
+
getEditor().setTextCursorPosition("paragraph-0");
|
|
160
|
+
|
|
161
|
+
moveBlocksUp(getEditor());
|
|
162
|
+
|
|
163
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("Multiple blocks", () => {
|
|
167
|
+
getEditor().setSelection("paragraph-1", "paragraph-2");
|
|
168
|
+
|
|
169
|
+
moveBlocksUp(getEditor());
|
|
170
|
+
|
|
171
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("Multiple blocks starting in block with children", () => {
|
|
175
|
+
getEditor().setSelection("paragraph-with-children", "paragraph-2");
|
|
176
|
+
|
|
177
|
+
moveBlocksUp(getEditor());
|
|
178
|
+
|
|
179
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("Multiple blocks starting in nested block", () => {
|
|
183
|
+
getEditor().setSelection("nested-paragraph-0", "paragraph-2");
|
|
184
|
+
|
|
185
|
+
moveBlocksUp(getEditor());
|
|
186
|
+
|
|
187
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("Multiple blocks ending in block with children", () => {
|
|
191
|
+
getEditor().setSelection("paragraph-1", "paragraph-with-children");
|
|
192
|
+
|
|
193
|
+
moveBlocksUp(getEditor());
|
|
194
|
+
|
|
195
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("Multiple blocks ending in nested block", () => {
|
|
199
|
+
getEditor().setSelection("paragraph-1", "nested-paragraph-0");
|
|
200
|
+
|
|
201
|
+
moveBlocksUp(getEditor());
|
|
202
|
+
|
|
203
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("Multiple blocks starting and ending in nested block", () => {
|
|
207
|
+
getEditor().setSelection("nested-paragraph-0", "nested-paragraph-1");
|
|
208
|
+
|
|
209
|
+
moveBlocksUp(getEditor());
|
|
210
|
+
|
|
211
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("Test moveBlocksDown", () => {
|
|
216
|
+
it("Basic", () => {
|
|
217
|
+
getEditor().setTextCursorPosition("paragraph-0");
|
|
218
|
+
|
|
219
|
+
moveBlocksDown(getEditor());
|
|
220
|
+
|
|
221
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("Into children", () => {
|
|
225
|
+
getEditor().setTextCursorPosition("paragraph-1");
|
|
226
|
+
|
|
227
|
+
moveBlocksDown(getEditor());
|
|
228
|
+
|
|
229
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("Out of children", () => {
|
|
233
|
+
getEditor().setTextCursorPosition("nested-paragraph-1");
|
|
234
|
+
|
|
235
|
+
moveBlocksDown(getEditor());
|
|
236
|
+
|
|
237
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("Last block", () => {
|
|
241
|
+
getEditor().setTextCursorPosition("trailing-paragraph");
|
|
242
|
+
|
|
243
|
+
moveBlocksDown(getEditor());
|
|
244
|
+
|
|
245
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("Multiple blocks", () => {
|
|
249
|
+
getEditor().setSelection("paragraph-1", "paragraph-2");
|
|
250
|
+
|
|
251
|
+
moveBlocksDown(getEditor());
|
|
252
|
+
|
|
253
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("Multiple blocks starting in block with children", () => {
|
|
257
|
+
getEditor().setSelection("paragraph-with-children", "paragraph-2");
|
|
258
|
+
|
|
259
|
+
moveBlocksDown(getEditor());
|
|
260
|
+
|
|
261
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("Multiple blocks starting in nested block", () => {
|
|
265
|
+
getEditor().setSelection("nested-paragraph-0", "paragraph-2");
|
|
266
|
+
|
|
267
|
+
moveBlocksDown(getEditor());
|
|
268
|
+
|
|
269
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("Multiple blocks ending in block with children", () => {
|
|
273
|
+
getEditor().setSelection("paragraph-1", "paragraph-with-children");
|
|
274
|
+
|
|
275
|
+
moveBlocksDown(getEditor());
|
|
276
|
+
|
|
277
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("Multiple blocks ending in nested block", () => {
|
|
281
|
+
getEditor().setSelection("paragraph-1", "nested-paragraph-0");
|
|
282
|
+
|
|
283
|
+
moveBlocksDown(getEditor());
|
|
284
|
+
|
|
285
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("Multiple blocks starting and ending in nested block", () => {
|
|
289
|
+
getEditor().setSelection("nested-paragraph-0", "nested-paragraph-1");
|
|
290
|
+
|
|
291
|
+
moveBlocksDown(getEditor());
|
|
292
|
+
|
|
293
|
+
expect(getEditor().document).toMatchSnapshot();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { NodeSelection, Selection, TextSelection } from "prosemirror-state";
|
|
2
|
+
import { CellSelection } from "prosemirror-tables";
|
|
3
|
+
|
|
4
|
+
import { Block } from "../../../../blocks/defaultBlocks.js";
|
|
5
|
+
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
|
|
6
|
+
import { BlockIdentifier } from "../../../../schema/index.js";
|
|
7
|
+
import { getNearestBlockPos } from "../../../getBlockInfoFromPos.js";
|
|
8
|
+
import { getNodeById } from "../../../nodeUtil.js";
|
|
9
|
+
|
|
10
|
+
type BlockSelectionData = (
|
|
11
|
+
| {
|
|
12
|
+
type: "text";
|
|
13
|
+
headBlockId: string;
|
|
14
|
+
anchorOffset: number;
|
|
15
|
+
headOffset: number;
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
type: "node";
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
type: "cell";
|
|
22
|
+
anchorCellOffset: number;
|
|
23
|
+
headCellOffset: number;
|
|
24
|
+
}
|
|
25
|
+
) & {
|
|
26
|
+
anchorBlockId: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `getBlockSelectionData` and `updateBlockSelectionFromData` are used to save
|
|
31
|
+
* and restore the selection within a block, when the block is moved. This is
|
|
32
|
+
* done by first saving the offsets of the anchor and head from the before
|
|
33
|
+
* positions of their surrounding blocks, as well as the IDs of those blocks. We
|
|
34
|
+
* can then recreate the selection by finding the blocks with those IDs, getting
|
|
35
|
+
* their before positions, and adding the offsets to those positions.
|
|
36
|
+
* @param editor The BlockNote editor instance to get the selection data from.
|
|
37
|
+
*/
|
|
38
|
+
function getBlockSelectionData(
|
|
39
|
+
editor: BlockNoteEditor<any, any, any>
|
|
40
|
+
): BlockSelectionData {
|
|
41
|
+
const state = editor._tiptapEditor.state;
|
|
42
|
+
const selection = state.selection;
|
|
43
|
+
|
|
44
|
+
const anchorBlockPosInfo = getNearestBlockPos(state.doc, selection.anchor);
|
|
45
|
+
|
|
46
|
+
if (selection instanceof CellSelection) {
|
|
47
|
+
return {
|
|
48
|
+
type: "cell" as const,
|
|
49
|
+
anchorBlockId: anchorBlockPosInfo.node.attrs.id,
|
|
50
|
+
anchorCellOffset:
|
|
51
|
+
selection.$anchorCell.pos - anchorBlockPosInfo.posBeforeNode,
|
|
52
|
+
headCellOffset:
|
|
53
|
+
selection.$headCell.pos - anchorBlockPosInfo.posBeforeNode,
|
|
54
|
+
};
|
|
55
|
+
} else if (editor._tiptapEditor.state.selection instanceof NodeSelection) {
|
|
56
|
+
return {
|
|
57
|
+
type: "node" as const,
|
|
58
|
+
anchorBlockId: anchorBlockPosInfo.node.attrs.id,
|
|
59
|
+
};
|
|
60
|
+
} else {
|
|
61
|
+
const headBlockPosInfo = getNearestBlockPos(state.doc, selection.head);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
type: "text" as const,
|
|
65
|
+
anchorBlockId: anchorBlockPosInfo.node.attrs.id,
|
|
66
|
+
headBlockId: headBlockPosInfo.node.attrs.id,
|
|
67
|
+
anchorOffset: selection.anchor - anchorBlockPosInfo.posBeforeNode,
|
|
68
|
+
headOffset: selection.head - headBlockPosInfo.posBeforeNode,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `getBlockSelectionData` and `updateBlockSelectionFromData` are used to save
|
|
75
|
+
* and restore the selection within a block, when the block is moved. This is
|
|
76
|
+
* done by first saving the offsets of the anchor and head from the before
|
|
77
|
+
* positions of their surrounding blocks, as well as the IDs of those blocks. We
|
|
78
|
+
* can then recreate the selection by finding the blocks with those IDs, getting
|
|
79
|
+
* their before positions, and adding the offsets to those positions.
|
|
80
|
+
* @param editor The BlockNote editor instance to update the selection in.
|
|
81
|
+
* @param data The selection data to update the selection with (generated by
|
|
82
|
+
* `getBlockSelectionData`).
|
|
83
|
+
*/
|
|
84
|
+
function updateBlockSelectionFromData(
|
|
85
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
86
|
+
data: BlockSelectionData
|
|
87
|
+
) {
|
|
88
|
+
const anchorBlockPos = getNodeById(
|
|
89
|
+
data.anchorBlockId,
|
|
90
|
+
editor._tiptapEditor.state.doc
|
|
91
|
+
)?.posBeforeNode;
|
|
92
|
+
if (anchorBlockPos === undefined) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Could not find block with ID ${data.anchorBlockId} to update selection`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let selection: Selection;
|
|
99
|
+
if (data.type === "cell") {
|
|
100
|
+
selection = CellSelection.create(
|
|
101
|
+
editor._tiptapEditor.state.doc,
|
|
102
|
+
anchorBlockPos + data.anchorCellOffset,
|
|
103
|
+
anchorBlockPos + data.headCellOffset
|
|
104
|
+
);
|
|
105
|
+
} else if (data.type === "node") {
|
|
106
|
+
selection = NodeSelection.create(
|
|
107
|
+
editor._tiptapEditor.state.doc,
|
|
108
|
+
anchorBlockPos + 1
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
const headBlockPos = getNodeById(
|
|
112
|
+
data.headBlockId,
|
|
113
|
+
editor._tiptapEditor.state.doc
|
|
114
|
+
)?.posBeforeNode;
|
|
115
|
+
if (headBlockPos === undefined) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Could not find block with ID ${data.headBlockId} to update selection`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
selection = TextSelection.create(
|
|
122
|
+
editor._tiptapEditor.state.doc,
|
|
123
|
+
anchorBlockPos + data.anchorOffset,
|
|
124
|
+
headBlockPos + data.headOffset
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
editor._tiptapEditor.view.dispatch(
|
|
129
|
+
editor._tiptapEditor.state.tr.setSelection(selection)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Replaces any `columnList` blocks with the children of their columns. This is
|
|
135
|
+
* done here instead of in `getSelection` as we still need to remove the entire
|
|
136
|
+
* `columnList` node but only insert the `blockContainer` nodes inside it.
|
|
137
|
+
* @param blocks The blocks to flatten.
|
|
138
|
+
*/
|
|
139
|
+
function flattenColumns(
|
|
140
|
+
blocks: Block<any, any, any>[]
|
|
141
|
+
): Block<any, any, any>[] {
|
|
142
|
+
return blocks
|
|
143
|
+
.map((block) => {
|
|
144
|
+
if (block.type === "columnList") {
|
|
145
|
+
return block.children
|
|
146
|
+
.map((column) => flattenColumns(column.children))
|
|
147
|
+
.flat();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
...block,
|
|
152
|
+
children: flattenColumns(block.children),
|
|
153
|
+
};
|
|
154
|
+
})
|
|
155
|
+
.flat();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Removes the selected blocks from the editor, then inserts them before/after a
|
|
160
|
+
* reference block. Also updates the selection to match the original selection
|
|
161
|
+
* using `getBlockSelectionData` and `updateBlockSelectionFromData`.
|
|
162
|
+
* @param editor The BlockNote editor instance to move the blocks in.
|
|
163
|
+
* @param referenceBlock The reference block to insert the selected blocks
|
|
164
|
+
* before/after.
|
|
165
|
+
* @param placement Whether to insert the selected blocks before or after the
|
|
166
|
+
* reference block.
|
|
167
|
+
*/
|
|
168
|
+
export function moveSelectedBlocksAndSelection(
|
|
169
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
170
|
+
referenceBlock: BlockIdentifier,
|
|
171
|
+
placement: "before" | "after"
|
|
172
|
+
) {
|
|
173
|
+
const blocks = editor.getSelection()?.blocks || [
|
|
174
|
+
editor.getTextCursorPosition().block,
|
|
175
|
+
];
|
|
176
|
+
const selectionData = getBlockSelectionData(editor);
|
|
177
|
+
|
|
178
|
+
editor.removeBlocks(blocks);
|
|
179
|
+
editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement);
|
|
180
|
+
|
|
181
|
+
updateBlockSelectionFromData(editor, selectionData);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Checks if a block is in a valid place after being moved. This check is
|
|
185
|
+
// primitive at the moment and only returns false if the block's parent is a
|
|
186
|
+
// `columnList` block. This is because regular blocks cannot be direct children
|
|
187
|
+
// of `columnList` blocks.
|
|
188
|
+
function checkPlacementIsValid(parentBlock?: Block<any, any, any>): boolean {
|
|
189
|
+
return !parentBlock || parentBlock.type !== "columnList";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Gets the placement for moving a block up. This has 3 cases:
|
|
193
|
+
// 1. If the block has a previous sibling without children, the placement is
|
|
194
|
+
// before it.
|
|
195
|
+
// 2. If the block has a previous sibling with children, the placement is after
|
|
196
|
+
// the last child.
|
|
197
|
+
// 3. If the block has no previous sibling, but is nested, the placement is
|
|
198
|
+
// before its parent.
|
|
199
|
+
// If the placement is invalid, the function is called recursively until a valid
|
|
200
|
+
// placement is found. Returns undefined if no valid placement is found, meaning
|
|
201
|
+
// the block is already at the top of the document.
|
|
202
|
+
function getMoveUpPlacement(
|
|
203
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
204
|
+
prevBlock?: Block<any, any, any>,
|
|
205
|
+
parentBlock?: Block<any, any, any>
|
|
206
|
+
):
|
|
207
|
+
| { referenceBlock: BlockIdentifier; placement: "before" | "after" }
|
|
208
|
+
| undefined {
|
|
209
|
+
let referenceBlock: Block<any, any, any> | undefined;
|
|
210
|
+
let placement: "before" | "after" | undefined;
|
|
211
|
+
|
|
212
|
+
if (!prevBlock) {
|
|
213
|
+
if (parentBlock) {
|
|
214
|
+
referenceBlock = parentBlock;
|
|
215
|
+
placement = "before";
|
|
216
|
+
}
|
|
217
|
+
} else if (prevBlock.children.length > 0) {
|
|
218
|
+
referenceBlock = prevBlock.children[prevBlock.children.length - 1];
|
|
219
|
+
placement = "after";
|
|
220
|
+
} else {
|
|
221
|
+
referenceBlock = prevBlock;
|
|
222
|
+
placement = "before";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Case when the block is already at the top of the document.
|
|
226
|
+
if (!referenceBlock || !placement) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const referenceBlockParent = editor.getParentBlock(referenceBlock);
|
|
231
|
+
if (!checkPlacementIsValid(referenceBlockParent)) {
|
|
232
|
+
return getMoveUpPlacement(
|
|
233
|
+
editor,
|
|
234
|
+
placement === "after"
|
|
235
|
+
? referenceBlock
|
|
236
|
+
: editor.getPrevBlock(referenceBlock),
|
|
237
|
+
referenceBlockParent
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { referenceBlock, placement };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Gets the placement for moving a block down. This has 3 cases:
|
|
245
|
+
// 1. If the block has a next sibling without children, the placement is after
|
|
246
|
+
// it.
|
|
247
|
+
// 2. If the block has a next sibling with children, the placement is before the
|
|
248
|
+
// first child.
|
|
249
|
+
// 3. If the block has no next sibling, but is nested, the placement is
|
|
250
|
+
// after its parent.
|
|
251
|
+
// If the placement is invalid, the function is called recursively until a valid
|
|
252
|
+
// placement is found. Returns undefined if no valid placement is found, meaning
|
|
253
|
+
// the block is already at the bottom of the document.
|
|
254
|
+
function getMoveDownPlacement(
|
|
255
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
256
|
+
nextBlock?: Block<any, any, any>,
|
|
257
|
+
parentBlock?: Block<any, any, any>
|
|
258
|
+
):
|
|
259
|
+
| { referenceBlock: BlockIdentifier; placement: "before" | "after" }
|
|
260
|
+
| undefined {
|
|
261
|
+
let referenceBlock: Block<any, any, any> | undefined;
|
|
262
|
+
let placement: "before" | "after" | undefined;
|
|
263
|
+
|
|
264
|
+
if (!nextBlock) {
|
|
265
|
+
if (parentBlock) {
|
|
266
|
+
referenceBlock = parentBlock;
|
|
267
|
+
placement = "after";
|
|
268
|
+
}
|
|
269
|
+
} else if (nextBlock.children.length > 0) {
|
|
270
|
+
referenceBlock = nextBlock.children[0];
|
|
271
|
+
placement = "before";
|
|
272
|
+
} else {
|
|
273
|
+
referenceBlock = nextBlock;
|
|
274
|
+
placement = "after";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Case when the block is already at the bottom of the document.
|
|
278
|
+
if (!referenceBlock || !placement) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const referenceBlockParent = editor.getParentBlock(referenceBlock);
|
|
283
|
+
if (!checkPlacementIsValid(referenceBlockParent)) {
|
|
284
|
+
return getMoveDownPlacement(
|
|
285
|
+
editor,
|
|
286
|
+
placement === "before"
|
|
287
|
+
? referenceBlock
|
|
288
|
+
: editor.getNextBlock(referenceBlock),
|
|
289
|
+
referenceBlockParent
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { referenceBlock, placement };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function moveBlocksUp(editor: BlockNoteEditor<any, any, any>) {
|
|
297
|
+
const selection = editor.getSelection();
|
|
298
|
+
const block = selection?.blocks[0] || editor.getTextCursorPosition().block;
|
|
299
|
+
|
|
300
|
+
const moveUpPlacement = getMoveUpPlacement(
|
|
301
|
+
editor,
|
|
302
|
+
editor.getPrevBlock(block),
|
|
303
|
+
editor.getParentBlock(block)
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (!moveUpPlacement) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
moveSelectedBlocksAndSelection(
|
|
311
|
+
editor,
|
|
312
|
+
moveUpPlacement.referenceBlock,
|
|
313
|
+
moveUpPlacement.placement
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function moveBlocksDown(editor: BlockNoteEditor<any, any, any>) {
|
|
318
|
+
const selection = editor.getSelection();
|
|
319
|
+
const block =
|
|
320
|
+
selection?.blocks[selection?.blocks.length - 1] ||
|
|
321
|
+
editor.getTextCursorPosition().block;
|
|
322
|
+
|
|
323
|
+
const moveDownPlacement = getMoveDownPlacement(
|
|
324
|
+
editor,
|
|
325
|
+
editor.getNextBlock(block),
|
|
326
|
+
editor.getParentBlock(block)
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (!moveDownPlacement) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
moveSelectedBlocksAndSelection(
|
|
334
|
+
editor,
|
|
335
|
+
moveDownPlacement.referenceBlock,
|
|
336
|
+
moveDownPlacement.placement
|
|
337
|
+
);
|
|
338
|
+
}
|
|
@@ -28,6 +28,10 @@ function setSelectionWithOffset(
|
|
|
28
28
|
offset: number
|
|
29
29
|
) {
|
|
30
30
|
const posInfo = getNodeById(targetBlockId, doc);
|
|
31
|
+
if (!posInfo) {
|
|
32
|
+
throw new Error(`Block with ID ${targetBlockId} not found`);
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
const info = getBlockInfo(posInfo);
|
|
32
36
|
|
|
33
37
|
if (!info.isBlockContainer) {
|
|
@@ -263,15 +263,23 @@ export function updateBlock<
|
|
|
263
263
|
|
|
264
264
|
const id =
|
|
265
265
|
typeof blockToUpdate === "string" ? blockToUpdate : blockToUpdate.id;
|
|
266
|
-
|
|
266
|
+
|
|
267
|
+
const posInfo = getNodeById(id, ttEditor.state.doc);
|
|
268
|
+
if (!posInfo) {
|
|
269
|
+
throw new Error(`Block with ID ${id} not found`);
|
|
270
|
+
}
|
|
267
271
|
|
|
268
272
|
ttEditor.commands.command(({ state, dispatch }) => {
|
|
269
|
-
updateBlockCommand(
|
|
273
|
+
updateBlockCommand(
|
|
274
|
+
editor,
|
|
275
|
+
posInfo.posBeforeNode,
|
|
276
|
+
update
|
|
277
|
+
)({ state, dispatch });
|
|
270
278
|
return true;
|
|
271
279
|
});
|
|
272
280
|
|
|
273
281
|
const blockContainerNode = ttEditor.state.doc
|
|
274
|
-
.resolve(posBeforeNode + 1) // TODO: clean?
|
|
282
|
+
.resolve(posInfo.posBeforeNode + 1) // TODO: clean?
|
|
275
283
|
.node();
|
|
276
284
|
|
|
277
285
|
return nodeToBlock(
|