@blocknote/core 0.8.1 → 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 +1787 -1834
- 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 +3 -3
- package/src/BlockNoteEditor.ts +102 -38
- 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/Blocks/nodes/BlockContainer.ts +12 -3
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +92 -104
- package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +178 -134
- package/src/extensions/Placeholder/PlaceholderExtension.ts +2 -2
- package/src/extensions/{DraggableBlocks/DraggableBlocksPlugin.ts → SideMenu/SideMenuPlugin.ts} +173 -163
- 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 +333 -389
- package/types/src/BlockNoteEditor.d.ts +18 -10
- 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 +25 -19
- package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +2 -3
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +17 -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/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +1 -1
- 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 -20
- 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,50 +227,37 @@ 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
|
-
blockMenuFactory,
|
|
257
|
-
horizontalPosAnchoredAtRoot,
|
|
258
|
-
}: BlockMenuViewProps<BSchema>) {
|
|
259
|
-
this.editor = editor;
|
|
260
|
-
this.ttEditor = tiptapEditor;
|
|
261
|
-
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;
|
|
262
254
|
this.horizontalPosAnchor = (
|
|
263
|
-
this.
|
|
255
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
264
256
|
).getBoundingClientRect().x;
|
|
265
257
|
|
|
266
|
-
this.blockMenu = blockMenuFactory(this.getStaticParams());
|
|
267
|
-
|
|
268
258
|
document.body.addEventListener("drop", this.onDrop, true);
|
|
269
259
|
document.body.addEventListener("dragover", this.onDragOver);
|
|
270
|
-
this.
|
|
260
|
+
this.pmView.dom.addEventListener("dragstart", this.onDragStart);
|
|
271
261
|
|
|
272
262
|
// Shows or updates menu position whenever the cursor moves, if the menu isn't frozen.
|
|
273
263
|
document.body.addEventListener("mousemove", this.onMouseMove, true);
|
|
@@ -275,9 +265,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
275
265
|
// Makes menu scroll with the page.
|
|
276
266
|
document.addEventListener("scroll", this.onScroll);
|
|
277
267
|
|
|
278
|
-
// Hides and unfreezes the menu whenever the user
|
|
279
|
-
// TODO: Better integration with suggestions menu and only editor scope?
|
|
280
|
-
document.body.addEventListener("mousedown", this.onMouseDown, true);
|
|
268
|
+
// Hides and unfreezes the menu whenever the user presses a key.
|
|
281
269
|
document.body.addEventListener("keydown", this.onKeyDown, true);
|
|
282
270
|
}
|
|
283
271
|
|
|
@@ -294,10 +282,13 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
294
282
|
* when dragging / dropping to the side of the editor
|
|
295
283
|
*/
|
|
296
284
|
onDrop = (event: DragEvent) => {
|
|
285
|
+
this.editor._tiptapEditor.commands.blur();
|
|
286
|
+
|
|
297
287
|
if ((event as any).synthetic || !this.isDragging) {
|
|
298
288
|
return;
|
|
299
289
|
}
|
|
300
|
-
|
|
290
|
+
|
|
291
|
+
let pos = this.pmView.posAtCoords({
|
|
301
292
|
left: event.clientX,
|
|
302
293
|
top: event.clientY,
|
|
303
294
|
});
|
|
@@ -307,7 +298,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
307
298
|
if (!pos || pos.inside === -1) {
|
|
308
299
|
const evt = new Event("drop", event) as any;
|
|
309
300
|
const editorBoundingBox = (
|
|
310
|
-
this.
|
|
301
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
311
302
|
).getBoundingClientRect();
|
|
312
303
|
evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
|
|
313
304
|
evt.clientY = event.clientY;
|
|
@@ -315,7 +306,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
315
306
|
evt.preventDefault = () => event.preventDefault();
|
|
316
307
|
evt.synthetic = true; // prevent recursion
|
|
317
308
|
// console.log("dispatch fake drop");
|
|
318
|
-
this.
|
|
309
|
+
this.pmView.dom.dispatchEvent(evt);
|
|
319
310
|
}
|
|
320
311
|
};
|
|
321
312
|
|
|
@@ -328,7 +319,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
328
319
|
if ((event as any).synthetic || !this.isDragging) {
|
|
329
320
|
return;
|
|
330
321
|
}
|
|
331
|
-
let pos = this.
|
|
322
|
+
let pos = this.pmView.posAtCoords({
|
|
332
323
|
left: event.clientX,
|
|
333
324
|
top: event.clientY,
|
|
334
325
|
});
|
|
@@ -336,7 +327,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
336
327
|
if (!pos || pos.inside === -1) {
|
|
337
328
|
const evt = new Event("dragover", event) as any;
|
|
338
329
|
const editorBoundingBox = (
|
|
339
|
-
this.
|
|
330
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
340
331
|
).getBoundingClientRect();
|
|
341
332
|
evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
|
|
342
333
|
evt.clientY = event.clientY;
|
|
@@ -344,29 +335,15 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
344
335
|
evt.preventDefault = () => event.preventDefault();
|
|
345
336
|
evt.synthetic = true; // prevent recursion
|
|
346
337
|
// console.log("dispatch fake dragover");
|
|
347
|
-
this.
|
|
338
|
+
this.pmView.dom.dispatchEvent(evt);
|
|
348
339
|
}
|
|
349
340
|
};
|
|
350
341
|
|
|
351
342
|
onKeyDown = (_event: KeyboardEvent) => {
|
|
352
|
-
if (this.
|
|
353
|
-
this.
|
|
354
|
-
this.
|
|
343
|
+
if (this.sideMenuState?.show) {
|
|
344
|
+
this.sideMenuState.show = false;
|
|
345
|
+
this.updateSideMenu(this.sideMenuState);
|
|
355
346
|
}
|
|
356
|
-
|
|
357
|
-
this.menuFrozen = false;
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
onMouseDown = (event: MouseEvent) => {
|
|
361
|
-
if (this.blockMenu.element?.contains(event.target as HTMLElement)) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (this.menuOpen) {
|
|
366
|
-
this.menuOpen = false;
|
|
367
|
-
this.blockMenu.hide();
|
|
368
|
-
}
|
|
369
|
-
|
|
370
347
|
this.menuFrozen = false;
|
|
371
348
|
};
|
|
372
349
|
|
|
@@ -380,18 +357,19 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
380
357
|
// blockGroup that wraps all blocks in the editor) for more accurate side
|
|
381
358
|
// menu placement.
|
|
382
359
|
const editorBoundingBox = (
|
|
383
|
-
this.
|
|
360
|
+
this.pmView.dom.firstChild! as HTMLElement
|
|
384
361
|
).getBoundingClientRect();
|
|
385
362
|
// We want the full area of the editor to check if the cursor is hovering
|
|
386
363
|
// above it though.
|
|
387
|
-
const editorOuterBoundingBox =
|
|
388
|
-
this.ttEditor.view.dom.getBoundingClientRect();
|
|
364
|
+
const editorOuterBoundingBox = this.pmView.dom.getBoundingClientRect();
|
|
389
365
|
const cursorWithinEditor =
|
|
390
366
|
event.clientX >= editorOuterBoundingBox.left &&
|
|
391
367
|
event.clientX <= editorOuterBoundingBox.right &&
|
|
392
368
|
event.clientY >= editorOuterBoundingBox.top &&
|
|
393
369
|
event.clientY <= editorOuterBoundingBox.bottom;
|
|
394
370
|
|
|
371
|
+
const editorWrapper = this.pmView.dom.parentElement!;
|
|
372
|
+
|
|
395
373
|
// Doesn't update if the mouse hovers an element that's over the editor but
|
|
396
374
|
// isn't a part of it or the side menu.
|
|
397
375
|
if (
|
|
@@ -401,15 +379,14 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
401
379
|
event &&
|
|
402
380
|
event.target &&
|
|
403
381
|
// Element is outside the editor
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
!this.blockMenu.element?.contains(event.target as HTMLElement)
|
|
382
|
+
!(
|
|
383
|
+
editorWrapper === event.target ||
|
|
384
|
+
editorWrapper.contains(event.target as HTMLElement)
|
|
385
|
+
)
|
|
409
386
|
) {
|
|
410
|
-
if (this.
|
|
411
|
-
this.
|
|
412
|
-
this.
|
|
387
|
+
if (this.sideMenuState?.show) {
|
|
388
|
+
this.sideMenuState.show = false;
|
|
389
|
+
this.updateSideMenu(this.sideMenuState);
|
|
413
390
|
}
|
|
414
391
|
|
|
415
392
|
return;
|
|
@@ -422,13 +399,13 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
422
399
|
left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor
|
|
423
400
|
top: event.clientY,
|
|
424
401
|
};
|
|
425
|
-
const block = getDraggableBlockFromCoords(coords, this.
|
|
402
|
+
const block = getDraggableBlockFromCoords(coords, this.pmView);
|
|
426
403
|
|
|
427
404
|
// Closes the menu if the mouse cursor is beyond the editor vertically.
|
|
428
405
|
if (!block || !this.editor.isEditable) {
|
|
429
|
-
if (this.
|
|
430
|
-
this.
|
|
431
|
-
this.
|
|
406
|
+
if (this.sideMenuState?.show) {
|
|
407
|
+
this.sideMenuState.show = false;
|
|
408
|
+
this.updateSideMenu(this.sideMenuState);
|
|
432
409
|
}
|
|
433
410
|
|
|
434
411
|
return;
|
|
@@ -436,7 +413,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
436
413
|
|
|
437
414
|
// Doesn't update if the menu is already open and the mouse cursor is still hovering the same block.
|
|
438
415
|
if (
|
|
439
|
-
this.
|
|
416
|
+
this.sideMenuState?.show &&
|
|
440
417
|
this.hoveredBlock?.hasAttribute("data-id") &&
|
|
441
418
|
this.hoveredBlock?.getAttribute("data-id") === block.id
|
|
442
419
|
) {
|
|
@@ -454,52 +431,69 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
454
431
|
|
|
455
432
|
// Shows or updates elements.
|
|
456
433
|
if (this.editor.isEditable) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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);
|
|
463
452
|
}
|
|
464
453
|
};
|
|
465
454
|
|
|
466
455
|
onScroll = () => {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
this.ttEditor.view.dom.firstChild! as HTMLElement
|
|
471
|
-
).getBoundingClientRect();
|
|
456
|
+
if (this.sideMenuState?.show) {
|
|
457
|
+
const blockContent = this.hoveredBlock!.firstChild as HTMLElement;
|
|
458
|
+
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
472
459
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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);
|
|
477
469
|
}
|
|
478
470
|
};
|
|
479
471
|
|
|
480
472
|
destroy() {
|
|
481
|
-
if (this.
|
|
482
|
-
this.
|
|
483
|
-
this.
|
|
473
|
+
if (this.sideMenuState?.show) {
|
|
474
|
+
this.sideMenuState.show = false;
|
|
475
|
+
this.updateSideMenu(this.sideMenuState);
|
|
484
476
|
}
|
|
485
477
|
document.body.removeEventListener("mousemove", this.onMouseMove);
|
|
486
478
|
document.body.removeEventListener("dragover", this.onDragOver);
|
|
487
|
-
this.
|
|
488
|
-
document.body.removeEventListener("drop", this.onDrop);
|
|
489
|
-
document.body.removeEventListener("mousedown", this.onMouseDown);
|
|
479
|
+
this.pmView.dom.removeEventListener("dragstart", this.onDragStart);
|
|
480
|
+
document.body.removeEventListener("drop", this.onDrop, true);
|
|
490
481
|
document.removeEventListener("scroll", this.onScroll);
|
|
491
|
-
document.body.removeEventListener("keydown", this.onKeyDown);
|
|
482
|
+
document.body.removeEventListener("keydown", this.onKeyDown, true);
|
|
492
483
|
}
|
|
493
484
|
|
|
494
485
|
addBlock() {
|
|
495
|
-
this.
|
|
486
|
+
if (this.sideMenuState?.show) {
|
|
487
|
+
this.sideMenuState.show = false;
|
|
488
|
+
this.updateSideMenu(this.sideMenuState);
|
|
489
|
+
}
|
|
490
|
+
|
|
496
491
|
this.menuFrozen = true;
|
|
497
|
-
this.blockMenu.hide();
|
|
498
492
|
|
|
499
493
|
const blockContent = this.hoveredBlock!.firstChild! as HTMLElement;
|
|
500
494
|
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
501
495
|
|
|
502
|
-
const pos = this.
|
|
496
|
+
const pos = this.pmView.posAtCoords({
|
|
503
497
|
left: blockContentBoundingBox.left + blockContentBoundingBox.width / 2,
|
|
504
498
|
top: blockContentBoundingBox.top + blockContentBoundingBox.height / 2,
|
|
505
499
|
});
|
|
@@ -507,7 +501,10 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
507
501
|
return;
|
|
508
502
|
}
|
|
509
503
|
|
|
510
|
-
const blockInfo = getBlockInfoFromPos(
|
|
504
|
+
const blockInfo = getBlockInfoFromPos(
|
|
505
|
+
this.editor._tiptapEditor.state.doc,
|
|
506
|
+
pos.pos
|
|
507
|
+
);
|
|
511
508
|
if (blockInfo === undefined) {
|
|
512
509
|
return;
|
|
513
510
|
}
|
|
@@ -519,75 +516,88 @@ export class BlockMenuView<BSchema extends BlockSchema> {
|
|
|
519
516
|
const newBlockInsertionPos = endPos + 1;
|
|
520
517
|
const newBlockContentPos = newBlockInsertionPos + 2;
|
|
521
518
|
|
|
522
|
-
this.
|
|
519
|
+
this.editor._tiptapEditor
|
|
523
520
|
.chain()
|
|
524
521
|
.BNCreateBlock(newBlockInsertionPos)
|
|
525
522
|
.BNUpdateBlock(newBlockContentPos, { type: "paragraph", props: {} })
|
|
526
523
|
.setTextSelection(newBlockContentPos)
|
|
527
524
|
.run();
|
|
528
525
|
} else {
|
|
529
|
-
this.
|
|
526
|
+
this.editor._tiptapEditor.commands.setTextSelection(endPos);
|
|
530
527
|
}
|
|
531
528
|
|
|
532
529
|
// Focuses and activates the suggestion menu.
|
|
533
|
-
this.
|
|
534
|
-
this.
|
|
535
|
-
this.
|
|
530
|
+
this.pmView.focus();
|
|
531
|
+
this.pmView.dispatch(
|
|
532
|
+
this.pmView.state.tr.scrollIntoView().setMeta(slashMenuPluginKey, {
|
|
536
533
|
// TODO import suggestion plugin key
|
|
537
534
|
activate: true,
|
|
538
535
|
type: "drag",
|
|
539
536
|
})
|
|
540
537
|
);
|
|
541
538
|
}
|
|
539
|
+
}
|
|
542
540
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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);
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
return this.sideMenuView;
|
|
551
562
|
},
|
|
552
|
-
|
|
553
|
-
freezeMenu: () => {
|
|
554
|
-
this.menuFrozen = true;
|
|
555
|
-
},
|
|
556
|
-
unfreezeMenu: () => {
|
|
557
|
-
this.menuFrozen = false;
|
|
558
|
-
},
|
|
559
|
-
};
|
|
563
|
+
});
|
|
560
564
|
}
|
|
561
565
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
565
|
-
|
|
566
|
-
return {
|
|
567
|
-
block: this.editor.getBlock(this.hoveredBlock!.getAttribute("data-id")!)!,
|
|
568
|
-
referenceRect: new DOMRect(
|
|
569
|
-
this.horizontalPosAnchoredAtRoot
|
|
570
|
-
? this.horizontalPosAnchor
|
|
571
|
-
: blockContentBoundingBox.x,
|
|
572
|
-
blockContentBoundingBox.y,
|
|
573
|
-
blockContentBoundingBox.width,
|
|
574
|
-
blockContentBoundingBox.height
|
|
575
|
-
),
|
|
576
|
-
};
|
|
566
|
+
public onUpdate(callback: (state: SideMenuState<BSchema>) => void) {
|
|
567
|
+
return this.on("update", callback);
|
|
577
568
|
}
|
|
578
|
-
}
|
|
579
569
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
})
|
|
593
|
-
|
|
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
|
+
}
|