@blocknote/core 0.8.2 → 0.8.3
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/README.md +4 -0
- package/dist/blocknote.js +1777 -1849
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +4 -4
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/BlockNoteEditor.ts +89 -39
- package/src/BlockNoteExtensions.ts +1 -58
- package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +10 -10
- package/src/api/formatConversions/formatConversions.test.ts +587 -605
- package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +15 -15
- package/src/api/nodeConversions/nodeConversions.test.ts +90 -94
- package/src/extensions/Blocks/api/blockTypes.ts +3 -2
- package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +6 -0
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +101 -114
- package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +184 -149
- package/src/extensions/Placeholder/PlaceholderExtension.ts +2 -2
- package/src/extensions/{DraggableBlocks/DraggableBlocksPlugin.ts → SideMenu/SideMenuPlugin.ts} +181 -164
- package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +7 -30
- package/src/extensions/SlashMenu/SlashMenuPlugin.ts +51 -0
- package/src/extensions/SlashMenu/defaultSlashMenuItems.ts +109 -0
- package/src/extensions/UniqueID/UniqueID.ts +29 -30
- package/src/index.ts +9 -8
- package/src/node_modules/.vitest/results.json +1 -0
- package/src/shared/BaseUiElementTypes.ts +8 -0
- package/src/shared/EditorElement.ts +0 -16
- package/src/shared/EventEmitter.ts +58 -0
- package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -6
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +341 -403
- package/types/src/BlockNoteEditor.d.ts +18 -11
- package/types/src/BlockNoteExtensions.d.ts +0 -19
- package/types/src/EventEmitter.d.ts +11 -0
- package/types/src/extensions/Blocks/api/blockTypes.d.ts +3 -2
- package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +0 -17
- package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +26 -20
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +18 -24
- package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +0 -12
- package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +37 -10
- package/types/src/extensions/SideMenu/MultipleNodeSelection.d.ts +24 -0
- package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +79 -0
- package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +5 -18
- package/types/src/extensions/SlashMenu/SlashMenuPlugin.d.ts +13 -0
- package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +1 -69
- package/types/src/extensions/SlashMenu/index.d.ts +2 -3
- package/types/src/index.d.ts +9 -8
- package/types/src/shared/BaseUiElementTypes.d.ts +7 -0
- package/types/src/shared/EditorElement.d.ts +0 -10
- package/types/src/shared/EventEmitter.d.ts +11 -0
- package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -7
- package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +12 -43
- package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +0 -29
- package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +0 -37
- package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +0 -37
- package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +0 -18
- package/src/extensions/HyperlinkToolbar/HyperlinkMark.ts +0 -28
- package/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts +0 -19
- package/src/extensions/SlashMenu/SlashMenuExtension.ts +0 -53
- package/src/extensions/SlashMenu/defaultSlashMenuItems.tsx +0 -195
- package/src/extensions/SlashMenu/index.ts +0 -5
- package/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.ts +0 -21
- package/types/src/CustomBlock.d.ts +0 -15
- package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableCol.d.ts +0 -2
- package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableContent.d.ts +0 -2
- package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableRow.d.ts +0 -2
- package/types/src/extensions/Placeholder/localisation/index.d.ts +0 -2
- package/types/src/extensions/Placeholder/localisation/translation.d.ts +0 -51
- /package/src/extensions/{DraggableBlocks → SideMenu}/MultipleNodeSelection.ts +0 -0
package/src/extensions/{DraggableBlocks/DraggableBlocksPlugin.ts → SideMenu/SideMenuPlugin.ts}
RENAMED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PluginView } from "@tiptap/pm/state";
|
|
2
2
|
import { Node } from "prosemirror-model";
|
|
3
3
|
import { NodeSelection, Plugin, PluginKey, Selection } from "prosemirror-state";
|
|
4
4
|
import * as pv from "prosemirror-view";
|
|
5
5
|
import { EditorView } from "prosemirror-view";
|
|
6
|
+
import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
6
7
|
import styles from "../../editor.module.css";
|
|
8
|
+
import { BaseUiElementState } from "../../shared/BaseUiElementTypes";
|
|
9
|
+
import { EventEmitter } from "../../shared/EventEmitter";
|
|
10
|
+
import { Block, BlockSchema } from "../Blocks/api/blockTypes";
|
|
7
11
|
import { getBlockInfoFromPos } from "../Blocks/helpers/getBlockInfoFromPos";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
BlockSideMenu,
|
|
11
|
-
BlockSideMenuDynamicParams,
|
|
12
|
-
BlockSideMenuFactory,
|
|
13
|
-
BlockSideMenuStaticParams,
|
|
14
|
-
} from "./BlockSideMenuFactoryTypes";
|
|
15
|
-
import { DraggableBlocksOptions } from "./DraggableBlocksExtension";
|
|
12
|
+
import { slashMenuPluginKey } from "../SlashMenu/SlashMenuPlugin";
|
|
16
13
|
import { MultipleNodeSelection } from "./MultipleNodeSelection";
|
|
17
|
-
import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
18
|
-
import { BlockSchema } from "../Blocks/api/blockTypes";
|
|
19
14
|
|
|
20
15
|
const serializeForClipboard = (pv as any).__serializeForClipboard;
|
|
21
16
|
// code based on https://github.com/ueberdosis/tiptap/issues/323#issuecomment-506637799
|
|
22
17
|
|
|
23
18
|
let dragImageElement: Element | undefined;
|
|
24
19
|
|
|
20
|
+
export type SideMenuState<BSchema extends BlockSchema> = BaseUiElementState & {
|
|
21
|
+
// The block that the side menu is attached to.
|
|
22
|
+
block: Block<BSchema>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
25
|
function getDraggableBlockFromCoords(
|
|
26
26
|
coords: { left: number; top: number },
|
|
27
27
|
view: EditorView
|
|
@@ -176,7 +176,10 @@ function unsetDragImage() {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
function dragStart(
|
|
179
|
+
function dragStart(
|
|
180
|
+
e: { dataTransfer: DataTransfer | null; clientY: number },
|
|
181
|
+
view: EditorView
|
|
182
|
+
) {
|
|
180
183
|
if (!e.dataTransfer) {
|
|
181
184
|
return;
|
|
182
185
|
}
|
|
@@ -224,59 +227,45 @@ function dragStart(e: DragEvent, view: EditorView) {
|
|
|
224
227
|
}
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
export
|
|
228
|
-
|
|
229
|
-
editor: BlockNoteEditor<BSchema>;
|
|
230
|
-
blockMenuFactory: BlockSideMenuFactory<BSchema>;
|
|
231
|
-
horizontalPosAnchoredAtRoot: boolean;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
export class BlockMenuView<BSchema extends BlockSchema> {
|
|
235
|
-
editor: BlockNoteEditor<BSchema>;
|
|
236
|
-
private ttEditor: Editor;
|
|
230
|
+
export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
|
|
231
|
+
private sideMenuState?: SideMenuState<BSchema>;
|
|
237
232
|
|
|
238
233
|
// When true, the drag handle with be anchored at the same level as root elements
|
|
239
234
|
// When false, the drag handle with be just to the left of the element
|
|
240
|
-
|
|
235
|
+
// TODO: Is there any case where we want this to be false?
|
|
236
|
+
private horizontalPosAnchoredAtRoot: boolean;
|
|
237
|
+
private horizontalPosAnchor: number;
|
|
241
238
|
|
|
242
|
-
|
|
239
|
+
private hoveredBlock: HTMLElement | undefined;
|
|
243
240
|
|
|
244
|
-
|
|
241
|
+
// Used to check if currently dragged content comes from this editor instance.
|
|
242
|
+
public isDragging = false;
|
|
245
243
|
|
|
246
|
-
|
|
244
|
+
public menuFrozen = false;
|
|
247
245
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
tiptapEditor,
|
|
257
|
-
editor,
|
|
258
|
-
blockMenuFactory,
|
|
259
|
-
horizontalPosAnchoredAtRoot,
|
|
260
|
-
}: BlockMenuViewProps<BSchema>) {
|
|
261
|
-
this.editor = editor;
|
|
262
|
-
this.ttEditor = tiptapEditor;
|
|
263
|
-
this.horizontalPosAnchoredAtRoot = horizontalPosAnchoredAtRoot;
|
|
246
|
+
constructor(
|
|
247
|
+
private readonly editor: BlockNoteEditor<BSchema>,
|
|
248
|
+
private readonly pmView: EditorView,
|
|
249
|
+
private readonly updateSideMenu: (
|
|
250
|
+
sideMenuState: SideMenuState<BSchema>
|
|
251
|
+
) => void
|
|
252
|
+
) {
|
|
253
|
+
this.horizontalPosAnchoredAtRoot = true;
|
|
264
254
|
this.horizontalPosAnchor = (
|
|
265
|
-
this.
|
|
255
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
266
256
|
).getBoundingClientRect().x;
|
|
267
257
|
|
|
268
|
-
this.blockMenu = blockMenuFactory(this.getStaticParams());
|
|
269
|
-
|
|
270
258
|
document.body.addEventListener("drop", this.onDrop, true);
|
|
271
259
|
document.body.addEventListener("dragover", this.onDragOver);
|
|
272
|
-
this.
|
|
260
|
+
this.pmView.dom.addEventListener("dragstart", this.onDragStart);
|
|
273
261
|
|
|
274
262
|
// Shows or updates menu position whenever the cursor moves, if the menu isn't frozen.
|
|
275
263
|
document.body.addEventListener("mousemove", this.onMouseMove, true);
|
|
276
264
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
265
|
+
// Makes menu scroll with the page.
|
|
266
|
+
document.addEventListener("scroll", this.onScroll);
|
|
267
|
+
|
|
268
|
+
// Hides and unfreezes the menu whenever the user presses a key.
|
|
280
269
|
document.body.addEventListener("keydown", this.onKeyDown, true);
|
|
281
270
|
}
|
|
282
271
|
|
|
@@ -293,10 +282,13 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
293
282
|
* when dragging / dropping to the side of the editor
|
|
294
283
|
*/
|
|
295
284
|
onDrop = (event: DragEvent) => {
|
|
285
|
+
this.editor._tiptapEditor.commands.blur();
|
|
286
|
+
|
|
296
287
|
if ((event as any).synthetic || !this.isDragging) {
|
|
297
288
|
return;
|
|
298
289
|
}
|
|
299
|
-
|
|
290
|
+
|
|
291
|
+
let pos = this.pmView.posAtCoords({
|
|
300
292
|
left: event.clientX,
|
|
301
293
|
top: event.clientY,
|
|
302
294
|
});
|
|
@@ -306,7 +298,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
306
298
|
if (!pos || pos.inside === -1) {
|
|
307
299
|
const evt = new Event("drop", event) as any;
|
|
308
300
|
const editorBoundingBox = (
|
|
309
|
-
this.
|
|
301
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
310
302
|
).getBoundingClientRect();
|
|
311
303
|
evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
|
|
312
304
|
evt.clientY = event.clientY;
|
|
@@ -314,7 +306,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
314
306
|
evt.preventDefault = () => event.preventDefault();
|
|
315
307
|
evt.synthetic = true; // prevent recursion
|
|
316
308
|
// console.log("dispatch fake drop");
|
|
317
|
-
this.
|
|
309
|
+
this.pmView.dom.dispatchEvent(evt);
|
|
318
310
|
}
|
|
319
311
|
};
|
|
320
312
|
|
|
@@ -327,7 +319,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
327
319
|
if ((event as any).synthetic || !this.isDragging) {
|
|
328
320
|
return;
|
|
329
321
|
}
|
|
330
|
-
let pos = this.
|
|
322
|
+
let pos = this.pmView.posAtCoords({
|
|
331
323
|
left: event.clientX,
|
|
332
324
|
top: event.clientY,
|
|
333
325
|
});
|
|
@@ -335,7 +327,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
335
327
|
if (!pos || pos.inside === -1) {
|
|
336
328
|
const evt = new Event("dragover", event) as any;
|
|
337
329
|
const editorBoundingBox = (
|
|
338
|
-
this.
|
|
330
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
339
331
|
).getBoundingClientRect();
|
|
340
332
|
evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
|
|
341
333
|
evt.clientY = event.clientY;
|
|
@@ -343,29 +335,15 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
343
335
|
evt.preventDefault = () => event.preventDefault();
|
|
344
336
|
evt.synthetic = true; // prevent recursion
|
|
345
337
|
// console.log("dispatch fake dragover");
|
|
346
|
-
this.
|
|
338
|
+
this.pmView.dom.dispatchEvent(evt);
|
|
347
339
|
}
|
|
348
340
|
};
|
|
349
341
|
|
|
350
342
|
onKeyDown = (_event: KeyboardEvent) => {
|
|
351
|
-
if (this.
|
|
352
|
-
this.
|
|
353
|
-
this.
|
|
343
|
+
if (this.sideMenuState?.show) {
|
|
344
|
+
this.sideMenuState.show = false;
|
|
345
|
+
this.updateSideMenu(this.sideMenuState);
|
|
354
346
|
}
|
|
355
|
-
|
|
356
|
-
this.menuFrozen = false;
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
onMouseDown = (event: MouseEvent) => {
|
|
360
|
-
if (this.blockMenu.element?.contains(event.target as HTMLElement)) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (this.menuOpen) {
|
|
365
|
-
this.menuOpen = false;
|
|
366
|
-
this.blockMenu.hide();
|
|
367
|
-
}
|
|
368
|
-
|
|
369
347
|
this.menuFrozen = false;
|
|
370
348
|
};
|
|
371
349
|
|
|
@@ -379,18 +357,19 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
379
357
|
// blockGroup that wraps all blocks in the editor) for more accurate side
|
|
380
358
|
// menu placement.
|
|
381
359
|
const editorBoundingBox = (
|
|
382
|
-
this.
|
|
360
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
383
361
|
).getBoundingClientRect();
|
|
384
362
|
// We want the full area of the editor to check if the cursor is hovering
|
|
385
363
|
// above it though.
|
|
386
|
-
const editorOuterBoundingBox =
|
|
387
|
-
this.ttEditor.view.dom.getBoundingClientRect();
|
|
364
|
+
const editorOuterBoundingBox = this.pmView.dom.getBoundingClientRect();
|
|
388
365
|
const cursorWithinEditor =
|
|
389
366
|
event.clientX >= editorOuterBoundingBox.left &&
|
|
390
367
|
event.clientX <= editorOuterBoundingBox.right &&
|
|
391
368
|
event.clientY >= editorOuterBoundingBox.top &&
|
|
392
369
|
event.clientY <= editorOuterBoundingBox.bottom;
|
|
393
370
|
|
|
371
|
+
const editorWrapper = this.pmView.dom.parentElement!;
|
|
372
|
+
|
|
394
373
|
// Doesn't update if the mouse hovers an element that's over the editor but
|
|
395
374
|
// isn't a part of it or the side menu.
|
|
396
375
|
if (
|
|
@@ -400,15 +379,14 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
400
379
|
event &&
|
|
401
380
|
event.target &&
|
|
402
381
|
// Element is outside the editor
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
!this.blockMenu.element?.contains(event.target as HTMLElement)
|
|
382
|
+
!(
|
|
383
|
+
editorWrapper === event.target ||
|
|
384
|
+
editorWrapper.contains(event.target as HTMLElement)
|
|
385
|
+
)
|
|
408
386
|
) {
|
|
409
|
-
if (this.
|
|
410
|
-
this.
|
|
411
|
-
this.
|
|
387
|
+
if (this.sideMenuState?.show) {
|
|
388
|
+
this.sideMenuState.show = false;
|
|
389
|
+
this.updateSideMenu(this.sideMenuState);
|
|
412
390
|
}
|
|
413
391
|
|
|
414
392
|
return;
|
|
@@ -421,13 +399,13 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
421
399
|
left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor
|
|
422
400
|
top: event.clientY,
|
|
423
401
|
};
|
|
424
|
-
const block = getDraggableBlockFromCoords(coords, this.
|
|
402
|
+
const block = getDraggableBlockFromCoords(coords, this.pmView);
|
|
425
403
|
|
|
426
404
|
// Closes the menu if the mouse cursor is beyond the editor vertically.
|
|
427
405
|
if (!block || !this.editor.isEditable) {
|
|
428
|
-
if (this.
|
|
429
|
-
this.
|
|
430
|
-
this.
|
|
406
|
+
if (this.sideMenuState?.show) {
|
|
407
|
+
this.sideMenuState.show = false;
|
|
408
|
+
this.updateSideMenu(this.sideMenuState);
|
|
431
409
|
}
|
|
432
410
|
|
|
433
411
|
return;
|
|
@@ -435,7 +413,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
435
413
|
|
|
436
414
|
// Doesn't update if the menu is already open and the mouse cursor is still hovering the same block.
|
|
437
415
|
if (
|
|
438
|
-
this.
|
|
416
|
+
this.sideMenuState?.show &&
|
|
439
417
|
this.hoveredBlock?.hasAttribute("data-id") &&
|
|
440
418
|
this.hoveredBlock?.getAttribute("data-id") === block.id
|
|
441
419
|
) {
|
|
@@ -453,37 +431,69 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
453
431
|
|
|
454
432
|
// Shows or updates elements.
|
|
455
433
|
if (this.editor.isEditable) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
434
|
+
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
435
|
+
|
|
436
|
+
this.sideMenuState = {
|
|
437
|
+
show: true,
|
|
438
|
+
referencePos: new DOMRect(
|
|
439
|
+
this.horizontalPosAnchoredAtRoot
|
|
440
|
+
? this.horizontalPosAnchor
|
|
441
|
+
: blockContentBoundingBox.x,
|
|
442
|
+
blockContentBoundingBox.y,
|
|
443
|
+
blockContentBoundingBox.width,
|
|
444
|
+
blockContentBoundingBox.height
|
|
445
|
+
),
|
|
446
|
+
block: this.editor.getBlock(
|
|
447
|
+
this.hoveredBlock!.getAttribute("data-id")!
|
|
448
|
+
)!,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
this.updateSideMenu(this.sideMenuState);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
onScroll = () => {
|
|
456
|
+
if (this.sideMenuState?.show) {
|
|
457
|
+
const blockContent = this.hoveredBlock!.firstChild as HTMLElement;
|
|
458
|
+
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
459
|
+
|
|
460
|
+
this.sideMenuState.referencePos = new DOMRect(
|
|
461
|
+
this.horizontalPosAnchoredAtRoot
|
|
462
|
+
? this.horizontalPosAnchor
|
|
463
|
+
: blockContentBoundingBox.x,
|
|
464
|
+
blockContentBoundingBox.y,
|
|
465
|
+
blockContentBoundingBox.width,
|
|
466
|
+
blockContentBoundingBox.height
|
|
467
|
+
);
|
|
468
|
+
this.updateSideMenu(this.sideMenuState);
|
|
462
469
|
}
|
|
463
470
|
};
|
|
464
471
|
|
|
465
472
|
destroy() {
|
|
466
|
-
if (this.
|
|
467
|
-
this.
|
|
468
|
-
this.
|
|
473
|
+
if (this.sideMenuState?.show) {
|
|
474
|
+
this.sideMenuState.show = false;
|
|
475
|
+
this.updateSideMenu(this.sideMenuState);
|
|
469
476
|
}
|
|
470
477
|
document.body.removeEventListener("mousemove", this.onMouseMove);
|
|
471
478
|
document.body.removeEventListener("dragover", this.onDragOver);
|
|
472
|
-
this.
|
|
473
|
-
document.body.removeEventListener("drop", this.onDrop);
|
|
474
|
-
document.
|
|
475
|
-
document.body.removeEventListener("keydown", this.onKeyDown);
|
|
479
|
+
this.pmView.dom.removeEventListener("dragstart", this.onDragStart);
|
|
480
|
+
document.body.removeEventListener("drop", this.onDrop, true);
|
|
481
|
+
document.removeEventListener("scroll", this.onScroll);
|
|
482
|
+
document.body.removeEventListener("keydown", this.onKeyDown, true);
|
|
476
483
|
}
|
|
477
484
|
|
|
478
485
|
addBlock() {
|
|
479
|
-
this.
|
|
486
|
+
if (this.sideMenuState?.show) {
|
|
487
|
+
this.sideMenuState.show = false;
|
|
488
|
+
this.updateSideMenu(this.sideMenuState);
|
|
489
|
+
}
|
|
490
|
+
|
|
480
491
|
this.menuFrozen = true;
|
|
481
|
-
this.blockMenu.hide();
|
|
482
492
|
|
|
483
493
|
const blockContent = this.hoveredBlock!.firstChild! as HTMLElement;
|
|
484
494
|
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
485
495
|
|
|
486
|
-
const pos = this.
|
|
496
|
+
const pos = this.pmView.posAtCoords({
|
|
487
497
|
left: blockContentBoundingBox.left + blockContentBoundingBox.width / 2,
|
|
488
498
|
top: blockContentBoundingBox.top + blockContentBoundingBox.height / 2,
|
|
489
499
|
});
|
|
@@ -491,7 +501,10 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
491
501
|
return;
|
|
492
502
|
}
|
|
493
503
|
|
|
494
|
-
const blockInfo = getBlockInfoFromPos(
|
|
504
|
+
const blockInfo = getBlockInfoFromPos(
|
|
505
|
+
this.editor._tiptapEditor.state.doc,
|
|
506
|
+
pos.pos
|
|
507
|
+
);
|
|
495
508
|
if (blockInfo === undefined) {
|
|
496
509
|
return;
|
|
497
510
|
}
|
|
@@ -503,84 +516,88 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
503
516
|
const newBlockInsertionPos = endPos + 1;
|
|
504
517
|
const newBlockContentPos = newBlockInsertionPos + 2;
|
|
505
518
|
|
|
506
|
-
this.
|
|
519
|
+
this.editor._tiptapEditor
|
|
507
520
|
.chain()
|
|
508
521
|
.BNCreateBlock(newBlockInsertionPos)
|
|
509
522
|
.BNUpdateBlock(newBlockContentPos, { type: "paragraph", props: {} })
|
|
510
523
|
.setTextSelection(newBlockContentPos)
|
|
511
524
|
.run();
|
|
512
525
|
} else {
|
|
513
|
-
this.
|
|
526
|
+
this.editor._tiptapEditor.commands.setTextSelection(endPos);
|
|
514
527
|
}
|
|
515
528
|
|
|
516
529
|
// Focuses and activates the suggestion menu.
|
|
517
|
-
this.
|
|
518
|
-
this.
|
|
519
|
-
this.
|
|
530
|
+
this.pmView.focus();
|
|
531
|
+
this.pmView.dispatch(
|
|
532
|
+
this.pmView.state.tr.scrollIntoView().setMeta(slashMenuPluginKey, {
|
|
520
533
|
// TODO import suggestion plugin key
|
|
521
534
|
activate: true,
|
|
522
535
|
type: "drag",
|
|
523
536
|
})
|
|
524
537
|
);
|
|
525
538
|
}
|
|
539
|
+
}
|
|
526
540
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (this.lastPosition === undefined) {
|
|
546
|
-
throw new Error(
|
|
547
|
-
"Attempted to access block reference rect before rendering block side menu."
|
|
548
|
-
);
|
|
541
|
+
export const sideMenuPluginKey = new PluginKey("SideMenuPlugin");
|
|
542
|
+
|
|
543
|
+
export class SideMenuProsemirrorPlugin<
|
|
544
|
+
BSchema extends BlockSchema
|
|
545
|
+
> extends EventEmitter<any> {
|
|
546
|
+
private sideMenuView: SideMenuView<BSchema> | undefined;
|
|
547
|
+
public readonly plugin: Plugin;
|
|
548
|
+
|
|
549
|
+
constructor(private readonly editor: BlockNoteEditor<BSchema>) {
|
|
550
|
+
super();
|
|
551
|
+
this.plugin = new Plugin({
|
|
552
|
+
key: sideMenuPluginKey,
|
|
553
|
+
view: (editorView) => {
|
|
554
|
+
this.sideMenuView = new SideMenuView(
|
|
555
|
+
editor,
|
|
556
|
+
editorView,
|
|
557
|
+
(sideMenuState) => {
|
|
558
|
+
this.emit("update", sideMenuState);
|
|
549
559
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const blockContent = this.hoveredBlock!.firstChild! as HTMLElement;
|
|
555
|
-
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
556
|
-
if (this.horizontalPosAnchoredAtRoot) {
|
|
557
|
-
blockContentBoundingBox.x = this.horizontalPosAnchor;
|
|
558
|
-
}
|
|
559
|
-
this.lastPosition = blockContentBoundingBox;
|
|
560
|
-
|
|
561
|
-
return blockContentBoundingBox;
|
|
560
|
+
);
|
|
561
|
+
return this.sideMenuView;
|
|
562
562
|
},
|
|
563
|
-
};
|
|
563
|
+
});
|
|
564
564
|
}
|
|
565
565
|
|
|
566
|
-
|
|
567
|
-
return
|
|
568
|
-
block: this.editor.getBlock(this.hoveredBlock!.getAttribute("data-id")!)!,
|
|
569
|
-
};
|
|
566
|
+
public onUpdate(callback: (state: SideMenuState<BSchema>) => void) {
|
|
567
|
+
return this.on("update", callback);
|
|
570
568
|
}
|
|
571
|
-
}
|
|
572
569
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
})
|
|
586
|
-
|
|
570
|
+
/**
|
|
571
|
+
* If the block is empty, opens the slash menu. If the block has content,
|
|
572
|
+
* creates a new block below and opens the slash menu in it.
|
|
573
|
+
*/
|
|
574
|
+
addBlock = () => this.sideMenuView!.addBlock();
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Handles drag & drop events for blocks.
|
|
578
|
+
*/
|
|
579
|
+
blockDragStart = (event: {
|
|
580
|
+
dataTransfer: DataTransfer | null;
|
|
581
|
+
clientY: number;
|
|
582
|
+
}) => {
|
|
583
|
+
this.sideMenuView!.isDragging = true;
|
|
584
|
+
dragStart(event, this.editor.prosemirrorView);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Handles drag & drop events for blocks.
|
|
589
|
+
*/
|
|
590
|
+
blockDragEnd = () => unsetDragImage();
|
|
591
|
+
/**
|
|
592
|
+
* Freezes the side menu. When frozen, the side menu will stay
|
|
593
|
+
* attached to the same block regardless of which block is hovered by the
|
|
594
|
+
* mouse cursor.
|
|
595
|
+
*/
|
|
596
|
+
freezeMenu = () => (this.sideMenuView!.menuFrozen = true);
|
|
597
|
+
/**
|
|
598
|
+
* Unfreezes the side menu. When frozen, the side menu will stay
|
|
599
|
+
* attached to the same block regardless of which block is hovered by the
|
|
600
|
+
* mouse cursor.
|
|
601
|
+
*/
|
|
602
|
+
unfreezeMenu = () => (this.sideMenuView!.menuFrozen = false);
|
|
603
|
+
}
|
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
import { SuggestionItem } from "../../shared/plugins/suggestion/SuggestionItem";
|
|
2
2
|
import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
3
3
|
import { BlockSchema } from "../Blocks/api/blockTypes";
|
|
4
|
+
import { DefaultBlockSchema } from "../Blocks/api/defaultBlocks";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
BSchema extends BlockSchema
|
|
12
|
-
> extends SuggestionItem {
|
|
13
|
-
/**
|
|
14
|
-
* Constructs a new slash-command.
|
|
15
|
-
*
|
|
16
|
-
* @param name The name of the command
|
|
17
|
-
* @param execute The callback for creating a new node
|
|
18
|
-
* @param aliases Aliases for this command
|
|
19
|
-
*/
|
|
20
|
-
constructor(
|
|
21
|
-
public readonly name: string,
|
|
22
|
-
public readonly execute: (editor: BlockNoteEditor<BSchema>) => void,
|
|
23
|
-
public readonly aliases: string[] = []
|
|
24
|
-
) {
|
|
25
|
-
super(name, (query: string): boolean => {
|
|
26
|
-
return (
|
|
27
|
-
this.name.toLowerCase().startsWith(query.toLowerCase()) ||
|
|
28
|
-
this.aliases.filter((alias) =>
|
|
29
|
-
alias.toLowerCase().startsWith(query.toLowerCase())
|
|
30
|
-
).length !== 0
|
|
31
|
-
);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
6
|
+
export type BaseSlashMenuItem<
|
|
7
|
+
BSchema extends BlockSchema = DefaultBlockSchema
|
|
8
|
+
> = SuggestionItem & {
|
|
9
|
+
execute: (editor: BlockNoteEditor<BSchema>) => void;
|
|
10
|
+
aliases?: string[];
|
|
11
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
|
|
3
|
+
import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
4
|
+
import { EventEmitter } from "../../shared/EventEmitter";
|
|
5
|
+
import {
|
|
6
|
+
SuggestionsMenuState,
|
|
7
|
+
setupSuggestionsMenu,
|
|
8
|
+
} from "../../shared/plugins/suggestion/SuggestionPlugin";
|
|
9
|
+
import { BlockSchema } from "../Blocks/api/blockTypes";
|
|
10
|
+
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
11
|
+
|
|
12
|
+
export const slashMenuPluginKey = new PluginKey("SlashMenuPlugin");
|
|
13
|
+
|
|
14
|
+
export class SlashMenuProsemirrorPlugin<
|
|
15
|
+
BSchema extends BlockSchema,
|
|
16
|
+
SlashMenuItem extends BaseSlashMenuItem<BSchema>
|
|
17
|
+
> extends EventEmitter<any> {
|
|
18
|
+
public readonly plugin: Plugin;
|
|
19
|
+
public readonly itemCallback: (item: SlashMenuItem) => void;
|
|
20
|
+
|
|
21
|
+
constructor(editor: BlockNoteEditor<BSchema>, items: SlashMenuItem[]) {
|
|
22
|
+
super();
|
|
23
|
+
const suggestions = setupSuggestionsMenu<SlashMenuItem, BSchema>(
|
|
24
|
+
editor,
|
|
25
|
+
(state) => {
|
|
26
|
+
this.emit("update", state);
|
|
27
|
+
},
|
|
28
|
+
slashMenuPluginKey,
|
|
29
|
+
"/",
|
|
30
|
+
(query) =>
|
|
31
|
+
items.filter(
|
|
32
|
+
({ name, aliases }: SlashMenuItem) =>
|
|
33
|
+
name.toLowerCase().startsWith(query.toLowerCase()) ||
|
|
34
|
+
(aliases &&
|
|
35
|
+
aliases.filter((alias) =>
|
|
36
|
+
alias.toLowerCase().startsWith(query.toLowerCase())
|
|
37
|
+
).length !== 0)
|
|
38
|
+
),
|
|
39
|
+
({ item, editor }) => item.execute(editor)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
this.plugin = suggestions.plugin;
|
|
43
|
+
this.itemCallback = suggestions.itemCallback;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public onUpdate(
|
|
47
|
+
callback: (state: SuggestionsMenuState<SlashMenuItem>) => void
|
|
48
|
+
) {
|
|
49
|
+
return this.on("update", callback);
|
|
50
|
+
}
|
|
51
|
+
}
|