@blocknote/core 0.19.2 → 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 +1721 -1453
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +7 -7
- 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/nodeUtil.js +1 -1
- package/dist/src/api/nodeUtil.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 +56 -57
- package/dist/src/editor/BlockNoteEditor.js.map +1 -1
- package/dist/src/editor/BlockNoteExtensions.js +1 -0
- 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/dragging.js +5 -1
- package/dist/src/extensions/SideMenu/dragging.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/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/nodeUtil.ts +2 -2
- package/src/blocks/TableBlockContent/TableExtension.ts +12 -1
- package/src/editor/BlockNoteEditor.ts +87 -85
- package/src/editor/BlockNoteExtensions.ts +2 -0
- 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/dragging.ts +5 -1
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +34 -9
- package/src/i18n/locales/ru.ts +1 -1
- 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/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,244 @@
|
|
|
1
|
+
import { TextSelection } from "prosemirror-state";
|
|
2
|
+
import { TableMap } from "prosemirror-tables";
|
|
3
|
+
|
|
4
|
+
import { Block } from "../../../blocks/defaultBlocks.js";
|
|
5
|
+
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
|
|
6
|
+
import { Selection } from "../../../editor/selectionTypes.js";
|
|
7
|
+
import {
|
|
8
|
+
BlockIdentifier,
|
|
9
|
+
BlockSchema,
|
|
10
|
+
InlineContentSchema,
|
|
11
|
+
StyleSchema,
|
|
12
|
+
} from "../../../schema/index.js";
|
|
13
|
+
import { getBlockInfo, getNearestBlockPos } from "../../getBlockInfoFromPos.js";
|
|
14
|
+
import { nodeToBlock } from "../../nodeConversions/nodeToBlock.js";
|
|
15
|
+
import { getNodeById } from "../../nodeUtil.js";
|
|
16
|
+
|
|
17
|
+
export function getSelection<
|
|
18
|
+
BSchema extends BlockSchema,
|
|
19
|
+
I extends InlineContentSchema,
|
|
20
|
+
S extends StyleSchema
|
|
21
|
+
>(
|
|
22
|
+
editor: BlockNoteEditor<BSchema, I, S>
|
|
23
|
+
): Selection<BSchema, I, S> | undefined {
|
|
24
|
+
const state = editor._tiptapEditor.state;
|
|
25
|
+
|
|
26
|
+
const $startBlockBeforePos = state.doc.resolve(
|
|
27
|
+
getNearestBlockPos(state.doc, state.selection.from).posBeforeNode
|
|
28
|
+
);
|
|
29
|
+
const $endBlockBeforePos = state.doc.resolve(
|
|
30
|
+
getNearestBlockPos(state.doc, state.selection.to).posBeforeNode
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Return undefined if anchor and head are in the same block.
|
|
34
|
+
if ($startBlockBeforePos.pos === $endBlockBeforePos.pos) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Converts the node at the given index and depth around `$startBlockBeforePos`
|
|
39
|
+
// to a block. Used to get blocks at given indices at the shared depth and
|
|
40
|
+
// at the depth of `$startBlockBeforePos`.
|
|
41
|
+
const indexToBlock = (
|
|
42
|
+
index: number,
|
|
43
|
+
depth?: number
|
|
44
|
+
): Block<BSchema, I, S> => {
|
|
45
|
+
const pos = $startBlockBeforePos.posAtIndex(index, depth);
|
|
46
|
+
const node = state.doc.resolve(pos).nodeAfter;
|
|
47
|
+
|
|
48
|
+
if (!node) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Error getting selection - node not found at position ${pos}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return nodeToBlock(
|
|
55
|
+
node,
|
|
56
|
+
editor.schema.blockSchema,
|
|
57
|
+
editor.schema.inlineContentSchema,
|
|
58
|
+
editor.schema.styleSchema,
|
|
59
|
+
editor.blockCache
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const blocks: Block<BSchema, I, S>[] = [];
|
|
64
|
+
// Minimum depth at which the blocks share a common ancestor.
|
|
65
|
+
const sharedDepth = $startBlockBeforePos.sharedDepth($endBlockBeforePos.pos);
|
|
66
|
+
const startIndex = $startBlockBeforePos.index(sharedDepth);
|
|
67
|
+
const endIndex = $endBlockBeforePos.index(sharedDepth);
|
|
68
|
+
|
|
69
|
+
// In most cases, we want to return the blocks spanned by the selection at the
|
|
70
|
+
// shared depth. However, when the block in which the selection starts is at a
|
|
71
|
+
// higher depth than the shared depth, we omit the first block at the shared
|
|
72
|
+
// depth. Instead, we include the first block at its depth, and any blocks at
|
|
73
|
+
// a higher index up to the shared depth. The following example illustrates
|
|
74
|
+
// this:
|
|
75
|
+
// - id-0
|
|
76
|
+
// - id-1
|
|
77
|
+
// - >|id-2
|
|
78
|
+
// - id-3
|
|
79
|
+
// - id-4
|
|
80
|
+
// - id-5
|
|
81
|
+
// - id-6
|
|
82
|
+
// - id-7
|
|
83
|
+
// - id-8
|
|
84
|
+
// - id-9|<
|
|
85
|
+
// - id-10
|
|
86
|
+
// Here, each block is represented by its ID, and the selection is represented
|
|
87
|
+
// by the `>|` and `|<` markers. So the selection starts in block `id-2` and
|
|
88
|
+
// ends in block `id-8`. In this case, the shared depth is 0, since the blocks
|
|
89
|
+
// `id-6`, `id-7`, and `id-8` set the shared depth, as they are the least
|
|
90
|
+
// nested blocks spanned by the selection. Therefore, these blocks are all
|
|
91
|
+
// added to the `blocks` array. However, the selection starts in block `id-2`,
|
|
92
|
+
// which is at a higher depth than the shared depth. So we add block `id-2` to
|
|
93
|
+
// the `blocks` array, as well as any later siblings (in this case, `id-3`),
|
|
94
|
+
// and move up one level of depth. The ancestor of block `id-2` at this depth
|
|
95
|
+
// is block `id-1`, so we add all its later siblings to the `blocks` array as
|
|
96
|
+
// well, again moving up one level of depth. Since we're now at the shared
|
|
97
|
+
// depth, we are done. The final `blocks` array for this example would be:
|
|
98
|
+
// [ id-2, id-3, id-4, id-6, id-7, id-8, id-9 ]
|
|
99
|
+
if ($startBlockBeforePos.depth > sharedDepth) {
|
|
100
|
+
// Adds the block that the selection starts in.
|
|
101
|
+
blocks.push(
|
|
102
|
+
nodeToBlock(
|
|
103
|
+
$startBlockBeforePos.nodeAfter!,
|
|
104
|
+
editor.schema.blockSchema,
|
|
105
|
+
editor.schema.inlineContentSchema,
|
|
106
|
+
editor.schema.styleSchema,
|
|
107
|
+
editor.blockCache
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Traverses all depths from the depth of the block in which the selection
|
|
112
|
+
// starts, up to the shared depth.
|
|
113
|
+
for (let depth = $startBlockBeforePos.depth; depth > sharedDepth; depth--) {
|
|
114
|
+
const parentNode = $startBlockBeforePos.node(depth);
|
|
115
|
+
|
|
116
|
+
if (parentNode.type.isInGroup("childContainer")) {
|
|
117
|
+
const startIndexAtDepth = $startBlockBeforePos.index(depth) + 1;
|
|
118
|
+
const childCountAtDepth = $startBlockBeforePos.node(depth).childCount;
|
|
119
|
+
|
|
120
|
+
// Adds all blocks after the index of the block in which the selection
|
|
121
|
+
// starts (or its ancestors at lower depths).
|
|
122
|
+
for (let i = startIndexAtDepth; i < childCountAtDepth; i++) {
|
|
123
|
+
blocks.push(indexToBlock(i, depth));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// Adds the first block spanned by the selection at the shared depth.
|
|
129
|
+
blocks.push(indexToBlock(startIndex, sharedDepth));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Adds all blocks spanned by the selection at the shared depth, excluding
|
|
133
|
+
// the first.
|
|
134
|
+
for (let i = startIndex + 1; i <= endIndex; i++) {
|
|
135
|
+
blocks.push(indexToBlock(i, sharedDepth));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (blocks.length === 0) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Error getting selection - selection doesn't span any blocks (${state.selection})`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
blocks,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function setSelection<
|
|
150
|
+
BSchema extends BlockSchema,
|
|
151
|
+
I extends InlineContentSchema,
|
|
152
|
+
S extends StyleSchema
|
|
153
|
+
>(
|
|
154
|
+
editor: BlockNoteEditor<BSchema, I, S>,
|
|
155
|
+
startBlock: BlockIdentifier,
|
|
156
|
+
endBlock: BlockIdentifier
|
|
157
|
+
) {
|
|
158
|
+
const startBlockId =
|
|
159
|
+
typeof startBlock === "string" ? startBlock : startBlock.id;
|
|
160
|
+
const endBlockId = typeof endBlock === "string" ? endBlock : endBlock.id;
|
|
161
|
+
|
|
162
|
+
if (startBlockId === endBlockId) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Attempting to set selection with the same anchor and head blocks (id ${startBlockId})`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const doc = editor._tiptapEditor.state.doc;
|
|
169
|
+
|
|
170
|
+
const anchorPosInfo = getNodeById(startBlockId, doc);
|
|
171
|
+
if (!anchorPosInfo) {
|
|
172
|
+
throw new Error(`Block with ID ${startBlockId} not found`);
|
|
173
|
+
}
|
|
174
|
+
const headPosInfo = getNodeById(endBlockId, doc);
|
|
175
|
+
if (!headPosInfo) {
|
|
176
|
+
throw new Error(`Block with ID ${endBlockId} not found`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const anchorBlockInfo = getBlockInfo(anchorPosInfo);
|
|
180
|
+
const headBlockInfo = getBlockInfo(headPosInfo);
|
|
181
|
+
|
|
182
|
+
const anchorBlockConfig =
|
|
183
|
+
editor.schema.blockSchema[
|
|
184
|
+
anchorBlockInfo.blockNoteType as keyof typeof editor.schema.blockSchema
|
|
185
|
+
];
|
|
186
|
+
const headBlockConfig =
|
|
187
|
+
editor.schema.blockSchema[
|
|
188
|
+
headBlockInfo.blockNoteType as keyof typeof editor.schema.blockSchema
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
!anchorBlockInfo.isBlockContainer ||
|
|
193
|
+
anchorBlockConfig.content === "none"
|
|
194
|
+
) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Attempting to set selection anchor in block without content (id ${startBlockId})`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (!headBlockInfo.isBlockContainer || headBlockConfig.content === "none") {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Attempting to set selection anchor in block without content (id ${endBlockId})`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let startPos: number;
|
|
206
|
+
let endPos: number;
|
|
207
|
+
|
|
208
|
+
if (anchorBlockConfig.content === "table") {
|
|
209
|
+
const tableMap = TableMap.get(anchorBlockInfo.blockContent.node);
|
|
210
|
+
const firstCellPos =
|
|
211
|
+
anchorBlockInfo.blockContent.beforePos +
|
|
212
|
+
tableMap.positionAt(0, 0, anchorBlockInfo.blockContent.node) +
|
|
213
|
+
1;
|
|
214
|
+
startPos = firstCellPos + 2;
|
|
215
|
+
} else {
|
|
216
|
+
startPos = anchorBlockInfo.blockContent.beforePos + 1;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (headBlockConfig.content === "table") {
|
|
220
|
+
const tableMap = TableMap.get(headBlockInfo.blockContent.node);
|
|
221
|
+
const lastCellPos =
|
|
222
|
+
headBlockInfo.blockContent.beforePos +
|
|
223
|
+
tableMap.positionAt(
|
|
224
|
+
tableMap.height - 1,
|
|
225
|
+
tableMap.width - 1,
|
|
226
|
+
headBlockInfo.blockContent.node
|
|
227
|
+
) +
|
|
228
|
+
1;
|
|
229
|
+
const lastCellNodeSize = doc.resolve(lastCellPos).nodeAfter!.nodeSize;
|
|
230
|
+
endPos = lastCellPos + lastCellNodeSize - 2;
|
|
231
|
+
} else {
|
|
232
|
+
endPos = headBlockInfo.blockContent.afterPos - 1;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// TODO: We should polish up the `MultipleNodeSelection` and use that instead.
|
|
236
|
+
// Right now it's missing a few things like a jsonID and styling to show
|
|
237
|
+
// which nodes are selected. `TextSelection` is ok for now, but has the
|
|
238
|
+
// restriction that the start/end blocks must have content.
|
|
239
|
+
editor._tiptapEditor.dispatch(
|
|
240
|
+
editor._tiptapEditor.state.tr.setSelection(
|
|
241
|
+
TextSelection.create(editor._tiptapEditor.state.doc, startPos, endPos)
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
}
|
|
@@ -96,6 +96,10 @@ export function setTextCursorPosition<
|
|
|
96
96
|
const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id;
|
|
97
97
|
|
|
98
98
|
const posInfo = getNodeById(id, editor._tiptapEditor.state.doc);
|
|
99
|
+
if (!posInfo) {
|
|
100
|
+
throw new Error(`Block with ID ${id} not found`);
|
|
101
|
+
}
|
|
102
|
+
|
|
99
103
|
const info = getBlockInfo(posInfo);
|
|
100
104
|
|
|
101
105
|
const contentType: "none" | "inline" | "table" =
|
package/src/api/nodeUtil.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Node } from "prosemirror-model";
|
|
|
6
6
|
export function getNodeById(
|
|
7
7
|
id: string,
|
|
8
8
|
doc: Node
|
|
9
|
-
): { node: Node; posBeforeNode: number } {
|
|
9
|
+
): { node: Node; posBeforeNode: number } | undefined {
|
|
10
10
|
let targetNode: Node | undefined = undefined;
|
|
11
11
|
let posBeforeNode: number | undefined = undefined;
|
|
12
12
|
|
|
@@ -28,7 +28,7 @@ export function getNodeById(
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
if (targetNode === undefined || posBeforeNode === undefined) {
|
|
31
|
-
|
|
31
|
+
return undefined;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { callOrReturn, Extension, getExtensionField } from "@tiptap/core";
|
|
2
|
-
import { columnResizing, tableEditing } from "prosemirror-tables";
|
|
2
|
+
import { columnResizing, goToNextCell, tableEditing } from "prosemirror-tables";
|
|
3
3
|
|
|
4
4
|
export const RESIZE_MIN_WIDTH = 35;
|
|
5
5
|
export const EMPTY_CELL_WIDTH = 120;
|
|
@@ -53,6 +53,17 @@ export const TableExtension = Extension.create({
|
|
|
53
53
|
selectionIsInTableParagraphNode
|
|
54
54
|
);
|
|
55
55
|
},
|
|
56
|
+
// Enables navigating cells using the tab key.
|
|
57
|
+
Tab: () => {
|
|
58
|
+
return this.editor.commands.command(({ state, dispatch, view }) =>
|
|
59
|
+
goToNextCell(1)(state, dispatch, view)
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
"Shift-Tab": () => {
|
|
63
|
+
return this.editor.commands.command(({ state, dispatch, view }) =>
|
|
64
|
+
goToNextCell(-1)(state, dispatch, view)
|
|
65
|
+
);
|
|
66
|
+
},
|
|
56
67
|
};
|
|
57
68
|
},
|
|
58
69
|
|
|
@@ -9,11 +9,17 @@ import {
|
|
|
9
9
|
import { Node, Schema } from "prosemirror-model";
|
|
10
10
|
// import "./blocknote.css";
|
|
11
11
|
import * as Y from "yjs";
|
|
12
|
+
import {
|
|
13
|
+
getBlock,
|
|
14
|
+
getNextBlock,
|
|
15
|
+
getParentBlock,
|
|
16
|
+
getPrevBlock,
|
|
17
|
+
} from "../api/blockManipulation/getBlock/getBlock.js";
|
|
12
18
|
import { insertBlocks } from "../api/blockManipulation/commands/insertBlocks/insertBlocks.js";
|
|
13
19
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "../api/blockManipulation/commands/
|
|
20
|
+
moveBlocksDown,
|
|
21
|
+
moveBlocksUp,
|
|
22
|
+
} from "../api/blockManipulation/commands/moveBlocks/moveBlocks.js";
|
|
17
23
|
import {
|
|
18
24
|
canNestBlock,
|
|
19
25
|
canUnnestBlock,
|
|
@@ -28,6 +34,10 @@ import {
|
|
|
28
34
|
getTextCursorPosition,
|
|
29
35
|
setTextCursorPosition,
|
|
30
36
|
} from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js";
|
|
37
|
+
import {
|
|
38
|
+
getSelection,
|
|
39
|
+
setSelection,
|
|
40
|
+
} from "../api/blockManipulation/selections/selection.js";
|
|
31
41
|
import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
|
|
32
42
|
import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
|
|
33
43
|
import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
|
|
@@ -219,6 +229,21 @@ export type BlockNoteEditorOptions<
|
|
|
219
229
|
setIdAttribute?: boolean;
|
|
220
230
|
|
|
221
231
|
dropCursor?: (opts: any) => Plugin;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
Select desired behavior when pressing `Tab` (or `Shift-Tab`). Specifically,
|
|
235
|
+
what should happen when a user has selected multiple blocks while a toolbar
|
|
236
|
+
is open:
|
|
237
|
+
- `"prefer-navigate-ui"`: Change focus to the toolbar. The user needs to
|
|
238
|
+
first press `Escape` to close the toolbar, and can then indent multiple
|
|
239
|
+
blocks. Better for keyboard accessibility.
|
|
240
|
+
- `"prefer-indent"`: Regardless of whether toolbars are open, indent the
|
|
241
|
+
selection of blocks. In this case, it's not possible to navigate toolbars
|
|
242
|
+
with the keyboard.
|
|
243
|
+
|
|
244
|
+
@default "prefer-navigate-ui"
|
|
245
|
+
*/
|
|
246
|
+
tabBehavior: "prefer-navigate-ui" | "prefer-indent";
|
|
222
247
|
};
|
|
223
248
|
|
|
224
249
|
const blockNoteTipTapOptions = {
|
|
@@ -395,6 +420,7 @@ export class BlockNoteEditor<
|
|
|
395
420
|
tableHandles: checkDefaultBlockTypeInSchema("table", this),
|
|
396
421
|
dropCursor: this.options.dropCursor ?? dropCursor,
|
|
397
422
|
placeholders: newOptions.placeholders,
|
|
423
|
+
tabBehavior: newOptions.tabBehavior,
|
|
398
424
|
});
|
|
399
425
|
|
|
400
426
|
// add extensions from _tiptapOptions
|
|
@@ -610,39 +636,57 @@ export class BlockNoteEditor<
|
|
|
610
636
|
|
|
611
637
|
/**
|
|
612
638
|
* Gets a snapshot of an existing block from the editor.
|
|
613
|
-
* @param blockIdentifier The identifier of an existing block that should be
|
|
614
|
-
*
|
|
639
|
+
* @param blockIdentifier The identifier of an existing block that should be
|
|
640
|
+
* retrieved.
|
|
641
|
+
* @returns The block that matches the identifier, or `undefined` if no
|
|
642
|
+
* matching block was found.
|
|
615
643
|
*/
|
|
616
644
|
public getBlock(
|
|
617
645
|
blockIdentifier: BlockIdentifier
|
|
618
646
|
): Block<BSchema, ISchema, SSchema> | undefined {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
? blockIdentifier
|
|
622
|
-
: blockIdentifier.id;
|
|
623
|
-
let newBlock: Block<BSchema, ISchema, SSchema> | undefined = undefined;
|
|
624
|
-
|
|
625
|
-
this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
|
|
626
|
-
if (typeof newBlock !== "undefined") {
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (node.type.name !== "blockContainer" || node.attrs.id !== id) {
|
|
631
|
-
return true;
|
|
632
|
-
}
|
|
647
|
+
return getBlock(this, blockIdentifier);
|
|
648
|
+
}
|
|
633
649
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
650
|
+
/**
|
|
651
|
+
* Gets a snapshot of the previous sibling of an existing block from the
|
|
652
|
+
* editor.
|
|
653
|
+
* @param blockIdentifier The identifier of an existing block for which the
|
|
654
|
+
* previous sibling should be retrieved.
|
|
655
|
+
* @returns The previous sibling of the block that matches the identifier.
|
|
656
|
+
* `undefined` if no matching block was found, or it's the first child/block
|
|
657
|
+
* in the document.
|
|
658
|
+
*/
|
|
659
|
+
public getPrevBlock(
|
|
660
|
+
blockIdentifier: BlockIdentifier
|
|
661
|
+
): Block<BSchema, ISchema, SSchema> | undefined {
|
|
662
|
+
return getPrevBlock(this, blockIdentifier);
|
|
663
|
+
}
|
|
641
664
|
|
|
642
|
-
|
|
643
|
-
|
|
665
|
+
/**
|
|
666
|
+
* Gets a snapshot of the next sibling of an existing block from the editor.
|
|
667
|
+
* @param blockIdentifier The identifier of an existing block for which the
|
|
668
|
+
* next sibling should be retrieved.
|
|
669
|
+
* @returns The next sibling of the block that matches the identifier.
|
|
670
|
+
* `undefined` if no matching block was found, or it's the last child/block in
|
|
671
|
+
* the document.
|
|
672
|
+
*/
|
|
673
|
+
public getNextBlock(
|
|
674
|
+
blockIdentifier: BlockIdentifier
|
|
675
|
+
): Block<BSchema, ISchema, SSchema> | undefined {
|
|
676
|
+
return getNextBlock(this, blockIdentifier);
|
|
677
|
+
}
|
|
644
678
|
|
|
645
|
-
|
|
679
|
+
/**
|
|
680
|
+
* Gets a snapshot of the parent of an existing block from the editor.
|
|
681
|
+
* @param blockIdentifier The identifier of an existing block for which the
|
|
682
|
+
* parent should be retrieved.
|
|
683
|
+
* @returns The parent of the block that matches the identifier. `undefined`
|
|
684
|
+
* if no matching block was found, or the block isn't nested.
|
|
685
|
+
*/
|
|
686
|
+
public getParentBlock(
|
|
687
|
+
blockIdentifier: BlockIdentifier
|
|
688
|
+
): Block<BSchema, ISchema, SSchema> | undefined {
|
|
689
|
+
return getParentBlock(this, blockIdentifier);
|
|
646
690
|
}
|
|
647
691
|
|
|
648
692
|
/**
|
|
@@ -728,53 +772,11 @@ export class BlockNoteEditor<
|
|
|
728
772
|
* Gets a snapshot of the current selection.
|
|
729
773
|
*/
|
|
730
774
|
public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if (
|
|
734
|
-
this._tiptapEditor.state.selection.from ===
|
|
735
|
-
this._tiptapEditor.state.selection.to ||
|
|
736
|
-
"node" in this._tiptapEditor.state.selection
|
|
737
|
-
) {
|
|
738
|
-
return undefined;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
const blocks: Block<BSchema, ISchema, SSchema>[] = [];
|
|
742
|
-
|
|
743
|
-
// TODO: This adds all child blocks to the same array. Needs to find min
|
|
744
|
-
// depth and only add blocks at that depth.
|
|
745
|
-
this._tiptapEditor.state.doc.descendants((node, pos) => {
|
|
746
|
-
if (node.type.spec.group !== "blockContent") {
|
|
747
|
-
return true;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Fixed the block pos and size
|
|
751
|
-
// all block is wrapped with a blockContent wrapper
|
|
752
|
-
// and blockContent wrapper pos = inner block pos - 1
|
|
753
|
-
// blockContent wrapper end = inner block pos + nodeSize + 1
|
|
754
|
-
// need to add 1 to start and -1 to end
|
|
755
|
-
const end = pos + node.nodeSize - 1;
|
|
756
|
-
const start = pos + 1;
|
|
757
|
-
if (
|
|
758
|
-
end <= this._tiptapEditor.state.selection.from ||
|
|
759
|
-
start >= this._tiptapEditor.state.selection.to
|
|
760
|
-
) {
|
|
761
|
-
return true;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
blocks.push(
|
|
765
|
-
nodeToBlock(
|
|
766
|
-
this._tiptapEditor.state.doc.resolve(pos).node(),
|
|
767
|
-
this.schema.blockSchema,
|
|
768
|
-
this.schema.inlineContentSchema,
|
|
769
|
-
this.schema.styleSchema,
|
|
770
|
-
this.blockCache
|
|
771
|
-
)
|
|
772
|
-
);
|
|
773
|
-
|
|
774
|
-
return false;
|
|
775
|
-
});
|
|
775
|
+
return getSelection(this);
|
|
776
|
+
}
|
|
776
777
|
|
|
777
|
-
|
|
778
|
+
public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {
|
|
779
|
+
setSelection(this, startBlock, endBlock);
|
|
778
780
|
}
|
|
779
781
|
|
|
780
782
|
/**
|
|
@@ -1032,21 +1034,21 @@ export class BlockNoteEditor<
|
|
|
1032
1034
|
}
|
|
1033
1035
|
|
|
1034
1036
|
/**
|
|
1035
|
-
* Moves the
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1037
|
+
* Moves the selected blocks up. If the previous block has children, moves
|
|
1038
|
+
* them to the end of its children. If there is no previous block, but the
|
|
1039
|
+
* current blocks share a common parent, moves them out of & before it.
|
|
1038
1040
|
*/
|
|
1039
|
-
public
|
|
1040
|
-
|
|
1041
|
+
public moveBlocksUp() {
|
|
1042
|
+
moveBlocksUp(this);
|
|
1041
1043
|
}
|
|
1042
1044
|
|
|
1043
1045
|
/**
|
|
1044
|
-
* Moves the
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1046
|
+
* Moves the selected blocks down. If the next block has children, moves
|
|
1047
|
+
* them to the start of its children. If there is no next block, but the
|
|
1048
|
+
* current blocks share a common parent, moves them out of & after it.
|
|
1047
1049
|
*/
|
|
1048
|
-
public
|
|
1049
|
-
|
|
1050
|
+
public moveBlocksDown() {
|
|
1051
|
+
moveBlocksDown(this);
|
|
1050
1052
|
}
|
|
1051
1053
|
|
|
1052
1054
|
/**
|
|
@@ -67,6 +67,7 @@ type ExtensionOptions<
|
|
|
67
67
|
tableHandles: boolean;
|
|
68
68
|
dropCursor: (opts: any) => Plugin;
|
|
69
69
|
placeholders: Record<string | "default", string>;
|
|
70
|
+
tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
/**
|
|
@@ -200,6 +201,7 @@ const getTipTapExtensions = <
|
|
|
200
201
|
}),
|
|
201
202
|
KeyboardShortcutsExtension.configure({
|
|
202
203
|
editor: opts.editor,
|
|
204
|
+
tabBehavior: opts.tabBehavior,
|
|
203
205
|
}),
|
|
204
206
|
BlockGroup.configure({
|
|
205
207
|
domAttributes: opts.domAttributes,
|
|
@@ -139,9 +139,11 @@ export class FormattingToolbarView implements PluginView {
|
|
|
139
139
|
// Wrapping in a setTimeout gives enough time to wait for the blur event to
|
|
140
140
|
// occur before updating the toolbar.
|
|
141
141
|
const { state, composing } = view;
|
|
142
|
-
const {
|
|
142
|
+
const { selection } = state;
|
|
143
143
|
const isSame =
|
|
144
|
-
oldState &&
|
|
144
|
+
oldState &&
|
|
145
|
+
oldState.selection.from === state.selection.from &&
|
|
146
|
+
oldState.selection.to === state.selection.to;
|
|
145
147
|
|
|
146
148
|
if (composing || isSame) {
|
|
147
149
|
return;
|
|
@@ -16,6 +16,7 @@ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
|
16
16
|
|
|
17
17
|
export const KeyboardShortcutsExtension = Extension.create<{
|
|
18
18
|
editor: BlockNoteEditor<any, any, any>;
|
|
19
|
+
tabBehavior: "prefer-navigate-ui" | "prefer-indent";
|
|
19
20
|
}>({
|
|
20
21
|
priority: 50,
|
|
21
22
|
|
|
@@ -479,9 +480,10 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
479
480
|
// editor since the browser will try to use tab for keyboard navigation.
|
|
480
481
|
Tab: () => {
|
|
481
482
|
if (
|
|
482
|
-
this.options.
|
|
483
|
-
this.options.editor.
|
|
484
|
-
|
|
483
|
+
this.options.tabBehavior !== "prefer-indent" &&
|
|
484
|
+
(this.options.editor.formattingToolbar?.shown ||
|
|
485
|
+
this.options.editor.linkToolbar?.shown ||
|
|
486
|
+
this.options.editor.filePanel?.shown)
|
|
485
487
|
) {
|
|
486
488
|
// don't handle tabs if a toolbar is shown, so we can tab into / out of it
|
|
487
489
|
return false;
|
|
@@ -491,9 +493,10 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
491
493
|
},
|
|
492
494
|
"Shift-Tab": () => {
|
|
493
495
|
if (
|
|
494
|
-
this.options.
|
|
495
|
-
this.options.editor.
|
|
496
|
-
|
|
496
|
+
this.options.tabBehavior !== "prefer-indent" &&
|
|
497
|
+
(this.options.editor.formattingToolbar?.shown ||
|
|
498
|
+
this.options.editor.linkToolbar?.shown ||
|
|
499
|
+
this.options.editor.filePanel?.shown)
|
|
497
500
|
) {
|
|
498
501
|
// don't handle tabs if a toolbar is shown, so we can tab into / out of it
|
|
499
502
|
return false;
|
|
@@ -502,11 +505,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
502
505
|
return true;
|
|
503
506
|
},
|
|
504
507
|
"Shift-Mod-ArrowUp": () => {
|
|
505
|
-
this.options.editor.
|
|
508
|
+
this.options.editor.moveBlocksUp();
|
|
506
509
|
return true;
|
|
507
510
|
},
|
|
508
511
|
"Shift-Mod-ArrowDown": () => {
|
|
509
|
-
this.options.editor.
|
|
512
|
+
this.options.editor.moveBlocksDown();
|
|
510
513
|
return true;
|
|
511
514
|
},
|
|
512
515
|
};
|
|
@@ -2,7 +2,7 @@ import { getMarkRange, posToDOMRect, Range } from "@tiptap/core";
|
|
|
2
2
|
|
|
3
3
|
import { EditorView } from "@tiptap/pm/view";
|
|
4
4
|
import { Mark } from "prosemirror-model";
|
|
5
|
-
import { Plugin, PluginKey, PluginView } from "prosemirror-state";
|
|
5
|
+
import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
|
|
6
6
|
|
|
7
7
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
8
8
|
import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
|
|
@@ -52,7 +52,7 @@ class LinkToolbarView implements PluginView {
|
|
|
52
52
|
|
|
53
53
|
this.startMenuUpdateTimer = () => {
|
|
54
54
|
this.menuUpdateTimer = setTimeout(() => {
|
|
55
|
-
this.update();
|
|
55
|
+
this.update(this.pmView);
|
|
56
56
|
}, 250);
|
|
57
57
|
};
|
|
58
58
|
|
|
@@ -190,8 +190,15 @@ class LinkToolbarView implements PluginView {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
update() {
|
|
194
|
-
|
|
193
|
+
update(view: EditorView, oldState?: EditorState) {
|
|
194
|
+
const { state } = view;
|
|
195
|
+
|
|
196
|
+
const isSame =
|
|
197
|
+
oldState &&
|
|
198
|
+
oldState.selection.from === state.selection.from &&
|
|
199
|
+
oldState.selection.to === state.selection.to;
|
|
200
|
+
|
|
201
|
+
if (isSame || !this.pmView.hasFocus()) {
|
|
195
202
|
return;
|
|
196
203
|
}
|
|
197
204
|
|