@blocknote/core 0.30.1 → 0.31.1
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.cjs +9 -9
- package/dist/blocknote.cjs.map +1 -1
- package/dist/blocknote.js +2793 -2213
- package/dist/blocknote.js.map +1 -1
- package/dist/{en-D4taoCs4.cjs → en-BXVKCwYt.cjs} +2 -2
- package/dist/en-BXVKCwYt.cjs.map +1 -0
- package/dist/{en-B7ycW7c8.js → en-qGo6sk9V.js} +2 -3
- package/dist/en-qGo6sk9V.js.map +1 -0
- package/dist/locales.cjs +1 -1
- package/dist/locales.cjs.map +1 -1
- package/dist/locales.js +20 -39
- package/dist/locales.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 +5 -6
- package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +2 -3
- package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +1 -1
- package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2816 -0
- package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +158 -0
- package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +87 -17
- package/src/api/blockManipulation/selections/selection.ts +48 -1
- package/src/api/blockManipulation/selections/{textCursorPosition/textCursorPosition.ts → textCursorPosition.ts} +7 -7
- package/src/api/getBlockInfoFromPos.ts +1 -1
- package/src/api/nodeConversions/blockToNode.ts +5 -2
- package/src/api/nodeConversions/nodeToBlock.ts +203 -8
- package/src/api/pmUtil.ts +3 -3
- package/src/blocks/CodeBlockContent/CodeBlockContent.ts +6 -6
- package/src/blocks/FileBlockContent/helpers/render/createAddFileButton.ts +1 -1
- package/src/blocks/TableBlockContent/TableBlockContent.ts +32 -2
- package/src/editor/Block.css +27 -1
- package/src/editor/BlockNoteEditor.test.ts +7 -0
- package/src/editor/BlockNoteEditor.ts +124 -39
- package/src/editor/BlockNoteExtension.ts +26 -0
- package/src/editor/BlockNoteExtensions.ts +28 -12
- package/src/editor/BlockNoteTipTapEditor.ts +23 -2
- package/src/extensions/Collaboration/CursorPlugin.ts +13 -7
- package/src/extensions/Collaboration/ForkYDocPlugin.test.ts +166 -0
- package/src/extensions/Collaboration/ForkYDocPlugin.ts +174 -0
- package/src/extensions/Collaboration/SyncPlugin.ts +7 -4
- package/src/extensions/Collaboration/UndoPlugin.ts +7 -4
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +30 -0
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +30 -0
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +1 -0
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +1 -0
- package/src/extensions/Comments/CommentsPlugin.ts +79 -70
- package/src/extensions/FilePanel/FilePanelPlugin.ts +54 -49
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +60 -26
- package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +26 -21
- package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +49 -42
- package/src/extensions/Placeholder/PlaceholderPlugin.ts +115 -108
- package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +183 -170
- package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +26 -19
- package/src/extensions/SideMenu/SideMenuPlugin.ts +23 -18
- package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +172 -168
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +4 -4
- package/src/extensions/Suggestions/SuggestionMarks.ts +175 -0
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +157 -150
- package/src/i18n/locales/ar.ts +0 -1
- package/src/i18n/locales/de.ts +0 -1
- package/src/i18n/locales/en.ts +0 -1
- package/src/i18n/locales/es.ts +0 -1
- package/src/i18n/locales/fr.ts +0 -1
- package/src/i18n/locales/hr.ts +0 -1
- package/src/i18n/locales/is.ts +0 -1
- package/src/i18n/locales/it.ts +0 -1
- package/src/i18n/locales/ja.ts +0 -1
- package/src/i18n/locales/ko.ts +0 -1
- package/src/i18n/locales/nl.ts +0 -1
- package/src/i18n/locales/no.ts +0 -1
- package/src/i18n/locales/pl.ts +0 -1
- package/src/i18n/locales/pt.ts +0 -1
- package/src/i18n/locales/ru.ts +0 -1
- package/src/i18n/locales/sk.ts +0 -1
- package/src/i18n/locales/uk.ts +0 -1
- package/src/i18n/locales/vi.ts +0 -1
- package/src/i18n/locales/zh-tw.ts +0 -1
- package/src/i18n/locales/zh.ts +0 -1
- package/src/index.ts +18 -8
- package/src/pm-nodes/BlockContainer.ts +1 -1
- package/src/pm-nodes/BlockGroup.ts +1 -1
- package/src/pm-nodes/Doc.ts +1 -0
- package/types/src/api/blockManipulation/commands/insertBlocks/insertBlocks.d.ts +1 -1
- package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +3 -1
- package/types/src/api/blockManipulation/selections/selection.d.ts +10 -0
- package/types/src/api/blockManipulation/selections/{textCursorPosition/textCursorPosition.d.ts → textCursorPosition.d.ts} +2 -2
- package/types/src/api/nodeConversions/nodeToBlock.d.ts +39 -2
- package/types/src/api/pmUtil.d.ts +3 -3
- package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +9 -1
- package/types/src/editor/BlockNoteEditor.d.ts +62 -10
- package/types/src/editor/BlockNoteExtension.d.ts +9 -0
- package/types/src/editor/BlockNoteExtensions.d.ts +2 -2
- package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
- package/types/src/extensions/Collaboration/CursorPlugin.d.ts +3 -3
- package/types/src/extensions/Collaboration/ForkYDocPlugin.d.ts +41 -0
- package/types/src/extensions/Collaboration/SyncPlugin.d.ts +3 -3
- package/types/src/extensions/Collaboration/UndoPlugin.d.ts +3 -3
- package/types/src/extensions/Comments/CommentsPlugin.d.ts +3 -4
- package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +4 -4
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +6 -5
- package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +4 -4
- package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +3 -3
- package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +3 -3
- package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +3 -3
- package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +3 -3
- package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +4 -4
- package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +3 -4
- package/types/src/extensions/Suggestions/SuggestionMarks.d.ts +4 -0
- package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +6 -6
- package/types/src/i18n/locales/en.d.ts +0 -1
- package/types/src/i18n/locales/sk.d.ts +0 -1
- package/types/src/index.d.ts +15 -8
- package/dist/en-B7ycW7c8.js.map +0 -1
- package/dist/en-D4taoCs4.cjs.map +0 -1
- package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +0 -844
- package/src/api/blockManipulation/selections/selection.test.ts +0 -72
- package/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap +0 -316
- package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts +0 -74
- package/types/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.d.ts +0 -1
- /package/types/src/{api/blockManipulation/selections/selection.test.d.ts → extensions/Collaboration/ForkYDocPlugin.test.d.ts} +0 -0
|
@@ -33,12 +33,13 @@ import {
|
|
|
33
33
|
import { insertContentAt } from "../api/blockManipulation/insertContentAt.js";
|
|
34
34
|
import {
|
|
35
35
|
getSelection,
|
|
36
|
+
getSelectionCutBlocks,
|
|
36
37
|
setSelection,
|
|
37
38
|
} from "../api/blockManipulation/selections/selection.js";
|
|
38
39
|
import {
|
|
39
40
|
getTextCursorPosition,
|
|
40
41
|
setTextCursorPosition,
|
|
41
|
-
} from "../api/blockManipulation/selections/textCursorPosition
|
|
42
|
+
} from "../api/blockManipulation/selections/textCursorPosition.js";
|
|
42
43
|
import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
|
|
43
44
|
import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
|
|
44
45
|
import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
|
|
@@ -93,6 +94,7 @@ import {
|
|
|
93
94
|
import { Dictionary } from "../i18n/dictionary.js";
|
|
94
95
|
import { en } from "../i18n/locales/index.js";
|
|
95
96
|
|
|
97
|
+
import { redo, undo } from "@tiptap/pm/history";
|
|
96
98
|
import {
|
|
97
99
|
TextSelection,
|
|
98
100
|
type Command,
|
|
@@ -101,11 +103,10 @@ import {
|
|
|
101
103
|
} from "@tiptap/pm/state";
|
|
102
104
|
import { dropCursor } from "prosemirror-dropcursor";
|
|
103
105
|
import { EditorView } from "prosemirror-view";
|
|
104
|
-
import {
|
|
105
|
-
import { undo, redo } from "@tiptap/pm/history";
|
|
106
|
+
import { redoCommand, undoCommand, ySyncPluginKey } from "y-prosemirror";
|
|
106
107
|
import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js";
|
|
107
108
|
import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js";
|
|
108
|
-
import {
|
|
109
|
+
import { docToBlocks } from "../api/nodeConversions/nodeToBlock.js";
|
|
109
110
|
import {
|
|
110
111
|
BlocksChanged,
|
|
111
112
|
getBlocksChangedByTransaction,
|
|
@@ -113,20 +114,26 @@ import {
|
|
|
113
114
|
import { nestedListsToBlockNoteStructure } from "../api/parsers/html/util/nestedLists.js";
|
|
114
115
|
import { CodeBlockOptions } from "../blocks/CodeBlockContent/CodeBlockContent.js";
|
|
115
116
|
import type { ThreadStore, User } from "../comments/index.js";
|
|
116
|
-
import "../
|
|
117
|
+
import type { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
|
|
118
|
+
import type { ForkYDocPlugin } from "../extensions/Collaboration/ForkYDocPlugin.js";
|
|
117
119
|
import { EventEmitter } from "../util/EventEmitter.js";
|
|
118
|
-
import {
|
|
120
|
+
import { BlockNoteExtension } from "./BlockNoteExtension.js";
|
|
121
|
+
|
|
122
|
+
import "../style.css";
|
|
119
123
|
|
|
124
|
+
/**
|
|
125
|
+
* A factory function that returns a BlockNoteExtension
|
|
126
|
+
* This is useful so we can create extensions that require an editor instance
|
|
127
|
+
* in the constructor
|
|
128
|
+
*/
|
|
120
129
|
export type BlockNoteExtensionFactory = (
|
|
121
130
|
editor: BlockNoteEditor<any, any, any>,
|
|
122
131
|
) => BlockNoteExtension;
|
|
123
132
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
priority?: number;
|
|
129
|
-
};
|
|
133
|
+
/**
|
|
134
|
+
* We support Tiptap extensions and BlockNoteExtension based extensions
|
|
135
|
+
*/
|
|
136
|
+
export type SupportedExtension = AnyExtension | BlockNoteExtension;
|
|
130
137
|
|
|
131
138
|
export type BlockCache<
|
|
132
139
|
BSchema extends BlockSchema = any,
|
|
@@ -369,9 +376,23 @@ export type BlockNoteEditorOptions<
|
|
|
369
376
|
_tiptapOptions: Partial<EditorOptions>;
|
|
370
377
|
|
|
371
378
|
/**
|
|
372
|
-
* (experimental) add extra
|
|
379
|
+
* (experimental) add extra extensions to the editor
|
|
380
|
+
*
|
|
381
|
+
* @deprecated, should use `extensions` instead
|
|
382
|
+
*/
|
|
383
|
+
_extensions: Record<
|
|
384
|
+
string,
|
|
385
|
+
| { plugin: Plugin; priority?: number }
|
|
386
|
+
| ((editor: BlockNoteEditor<any, any, any>) => {
|
|
387
|
+
plugin: Plugin;
|
|
388
|
+
priority?: number;
|
|
389
|
+
})
|
|
390
|
+
>;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Register
|
|
373
394
|
*/
|
|
374
|
-
|
|
395
|
+
extensions: Array<BlockNoteExtension | BlockNoteExtensionFactory>;
|
|
375
396
|
|
|
376
397
|
/**
|
|
377
398
|
* Boolean indicating whether the editor is in headless mode.
|
|
@@ -404,7 +425,7 @@ export class BlockNoteEditor<
|
|
|
404
425
|
/**
|
|
405
426
|
* extensions that are added to the editor, can be tiptap extensions or prosemirror plugins
|
|
406
427
|
*/
|
|
407
|
-
public
|
|
428
|
+
public extensions: Record<string, SupportedExtension> = {};
|
|
408
429
|
|
|
409
430
|
/**
|
|
410
431
|
* Boolean indicating whether the editor is in headless mode.
|
|
@@ -473,8 +494,10 @@ export class BlockNoteEditor<
|
|
|
473
494
|
|
|
474
495
|
private readonly showSelectionPlugin: ShowSelectionPlugin;
|
|
475
496
|
|
|
476
|
-
|
|
477
|
-
|
|
497
|
+
/**
|
|
498
|
+
* The plugin for forking a document, only defined if in collaboration mode
|
|
499
|
+
*/
|
|
500
|
+
public readonly forkYDocPlugin?: ForkYDocPlugin;
|
|
478
501
|
/**
|
|
479
502
|
* The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
|
|
480
503
|
* This method should set when creating the editor as this is application-specific.
|
|
@@ -609,12 +632,49 @@ export class BlockNoteEditor<
|
|
|
609
632
|
});
|
|
610
633
|
|
|
611
634
|
// add extensions from options
|
|
612
|
-
|
|
635
|
+
for (let ext of newOptions.extensions || []) {
|
|
613
636
|
if (typeof ext === "function") {
|
|
614
637
|
// factory
|
|
615
638
|
ext = ext(this);
|
|
616
639
|
}
|
|
640
|
+
const key = (ext.constructor as any).key();
|
|
641
|
+
if (!key) {
|
|
642
|
+
throw new Error(
|
|
643
|
+
`Extension ${ext.constructor.name} does not have a key method`,
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
if (this.extensions[key]) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Extension ${ext.constructor.name} already exists with key ${key}`,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
617
651
|
this.extensions[key] = ext;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// (when passed in via the deprecated `_extensions` option)
|
|
655
|
+
Object.entries(newOptions._extensions || {}).forEach(([key, ext]) => {
|
|
656
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
657
|
+
const editor = this;
|
|
658
|
+
|
|
659
|
+
const instance = typeof ext === "function" ? ext(editor) : ext;
|
|
660
|
+
if (!("plugin" in instance)) {
|
|
661
|
+
// Assume it is an Extension/Mark/Node
|
|
662
|
+
this.extensions[key] = instance;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
this.extensions[key] = new (class extends BlockNoteExtension {
|
|
667
|
+
public static key() {
|
|
668
|
+
return key;
|
|
669
|
+
}
|
|
670
|
+
constructor() {
|
|
671
|
+
super();
|
|
672
|
+
this.addProsemirrorPlugin(instance.plugin);
|
|
673
|
+
}
|
|
674
|
+
public get priority() {
|
|
675
|
+
return instance.priority;
|
|
676
|
+
}
|
|
677
|
+
})();
|
|
618
678
|
});
|
|
619
679
|
|
|
620
680
|
this.formattingToolbar = this.extensions["formattingToolbar"] as any;
|
|
@@ -625,7 +685,7 @@ export class BlockNoteEditor<
|
|
|
625
685
|
this.tableHandles = this.extensions["tableHandles"] as any;
|
|
626
686
|
this.comments = this.extensions["comments"] as any;
|
|
627
687
|
this.showSelectionPlugin = this.extensions["showSelection"] as any;
|
|
628
|
-
this.
|
|
688
|
+
this.forkYDocPlugin = this.extensions["forkYDocPlugin"] as any;
|
|
629
689
|
|
|
630
690
|
if (newOptions.uploadFile) {
|
|
631
691
|
const uploadFile = newOptions.uploadFile;
|
|
@@ -691,20 +751,19 @@ export class BlockNoteEditor<
|
|
|
691
751
|
return ext;
|
|
692
752
|
}
|
|
693
753
|
|
|
694
|
-
if (!ext.
|
|
695
|
-
|
|
696
|
-
"Extension should either be a TipTap extension or a ProseMirror plugin in a plugin property",
|
|
697
|
-
);
|
|
754
|
+
if (ext instanceof BlockNoteExtension && !ext.plugins.length) {
|
|
755
|
+
return undefined;
|
|
698
756
|
}
|
|
699
757
|
|
|
700
758
|
// "blocknote" extensions (prosemirror plugins)
|
|
701
759
|
return Extension.create({
|
|
702
760
|
name: key,
|
|
703
761
|
priority: ext.priority,
|
|
704
|
-
addProseMirrorPlugins: () =>
|
|
762
|
+
addProseMirrorPlugins: () => ext.plugins,
|
|
705
763
|
});
|
|
706
764
|
}),
|
|
707
|
-
];
|
|
765
|
+
].filter((ext): ext is Extension => ext !== undefined);
|
|
766
|
+
|
|
708
767
|
const tiptapOptions: BlockNoteTipTapEditorOptions = {
|
|
709
768
|
...blockNoteTipTapOptions,
|
|
710
769
|
...newOptions._tiptapOptions,
|
|
@@ -865,6 +924,26 @@ export class BlockNoteEditor<
|
|
|
865
924
|
}
|
|
866
925
|
}
|
|
867
926
|
|
|
927
|
+
// TO DISCUSS
|
|
928
|
+
/**
|
|
929
|
+
* Shorthand to get a typed extension from the editor, by
|
|
930
|
+
* just passing in the extension class.
|
|
931
|
+
*
|
|
932
|
+
* @param ext - The extension class to get
|
|
933
|
+
* @param key - optional, the key of the extension in the extensions object (defaults to the extension name)
|
|
934
|
+
* @returns The extension instance
|
|
935
|
+
*/
|
|
936
|
+
public extension<T extends BlockNoteExtension>(
|
|
937
|
+
ext: { new (...args: any[]): T } & typeof BlockNoteExtension,
|
|
938
|
+
key = ext.key(),
|
|
939
|
+
): T {
|
|
940
|
+
const extension = this.extensions[key] as T;
|
|
941
|
+
if (!extension) {
|
|
942
|
+
throw new Error(`Extension ${key} not found`);
|
|
943
|
+
}
|
|
944
|
+
return extension;
|
|
945
|
+
}
|
|
946
|
+
|
|
868
947
|
/**
|
|
869
948
|
* Mount the editor to a parent DOM element. Call mount(undefined) to clean up
|
|
870
949
|
*
|
|
@@ -946,15 +1025,7 @@ export class BlockNoteEditor<
|
|
|
946
1025
|
*/
|
|
947
1026
|
public get document(): Block<BSchema, ISchema, SSchema>[] {
|
|
948
1027
|
return this.transact((tr) => {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
tr.doc.firstChild!.descendants((node) => {
|
|
952
|
-
blocks.push(nodeToBlock(node, this.pmSchema));
|
|
953
|
-
|
|
954
|
-
return false;
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
return blocks;
|
|
1028
|
+
return docToBlocks(tr.doc, this.pmSchema);
|
|
958
1029
|
});
|
|
959
1030
|
}
|
|
960
1031
|
|
|
@@ -1099,12 +1170,26 @@ export class BlockNoteEditor<
|
|
|
1099
1170
|
}
|
|
1100
1171
|
|
|
1101
1172
|
/**
|
|
1102
|
-
* Gets a snapshot of the current selection.
|
|
1173
|
+
* Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
|
|
1174
|
+
* that the selection spans across.
|
|
1175
|
+
*
|
|
1176
|
+
* If the selection starts / ends halfway through a block, the returned data will contain the entire block.
|
|
1103
1177
|
*/
|
|
1104
1178
|
public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {
|
|
1105
1179
|
return this.transact((tr) => getSelection(tr));
|
|
1106
1180
|
}
|
|
1107
1181
|
|
|
1182
|
+
/**
|
|
1183
|
+
* Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
|
|
1184
|
+
* that the selection spans across.
|
|
1185
|
+
*
|
|
1186
|
+
* If the selection starts / ends halfway through a block, the returned block will be
|
|
1187
|
+
* only the part of the block that is included in the selection.
|
|
1188
|
+
*/
|
|
1189
|
+
public getSelectionCutBlocks() {
|
|
1190
|
+
return this.transact((tr) => getSelectionCutBlocks(tr));
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1108
1193
|
/**
|
|
1109
1194
|
* Sets the selection to a range of blocks.
|
|
1110
1195
|
* @param startBlock The identifier of the block that should be the start of the selection.
|
|
@@ -1500,7 +1585,7 @@ export class BlockNoteEditor<
|
|
|
1500
1585
|
);
|
|
1501
1586
|
}
|
|
1502
1587
|
|
|
1503
|
-
this.
|
|
1588
|
+
(this.extensions["yCursorPlugin"] as CursorPlugin).updateUser(user);
|
|
1504
1589
|
}
|
|
1505
1590
|
|
|
1506
1591
|
/**
|
|
@@ -1595,8 +1680,8 @@ export class BlockNoteEditor<
|
|
|
1595
1680
|
if (!this.prosemirrorView) {
|
|
1596
1681
|
return undefined;
|
|
1597
1682
|
}
|
|
1598
|
-
|
|
1599
|
-
const { selection } =
|
|
1683
|
+
|
|
1684
|
+
const { selection } = this.prosemirrorState;
|
|
1600
1685
|
|
|
1601
1686
|
// support for CellSelections
|
|
1602
1687
|
const { ranges } = selection;
|
|
@@ -1641,7 +1726,7 @@ export class BlockNoteEditor<
|
|
|
1641
1726
|
if (pluginState?.deleteTriggerCharacter) {
|
|
1642
1727
|
tr.insertText(triggerCharacter);
|
|
1643
1728
|
}
|
|
1644
|
-
tr.scrollIntoView().setMeta(this.suggestionMenus.
|
|
1729
|
+
tr.scrollIntoView().setMeta(this.suggestionMenus.plugins[0], {
|
|
1645
1730
|
triggerCharacter: triggerCharacter,
|
|
1646
1731
|
deleteTriggerCharacter: pluginState?.deleteTriggerCharacter || false,
|
|
1647
1732
|
ignoreQueryLength: pluginState?.ignoreQueryLength || false,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Plugin } from "prosemirror-state";
|
|
2
|
+
import { EventEmitter } from "../util/EventEmitter.js";
|
|
3
|
+
|
|
4
|
+
export abstract class BlockNoteExtension<
|
|
5
|
+
TEvent extends Record<string, any> = any,
|
|
6
|
+
> extends EventEmitter<TEvent> {
|
|
7
|
+
public static key(): string {
|
|
8
|
+
throw new Error("You must implement the key method in your extension");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
protected addProsemirrorPlugin(plugin: Plugin) {
|
|
12
|
+
this.plugins.push(plugin);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public readonly plugins: Plugin[] = [];
|
|
16
|
+
public get priority(): number | undefined {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line
|
|
21
|
+
constructor(..._args: any[]) {
|
|
22
|
+
super();
|
|
23
|
+
// Allow subclasses to have constructors with parameters
|
|
24
|
+
// without this, we can't easily implement BlockNoteEditor.extension(MyExtension) pattern
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -9,13 +9,13 @@ import * as Y from "yjs";
|
|
|
9
9
|
import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDropExtension.js";
|
|
10
10
|
import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js";
|
|
11
11
|
import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
|
|
12
|
+
import type { ThreadStore } from "../comments/index.js";
|
|
12
13
|
import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
|
|
13
14
|
import { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
|
|
14
|
-
import { UndoPlugin } from "../extensions/Collaboration/UndoPlugin.js";
|
|
15
15
|
import { SyncPlugin } from "../extensions/Collaboration/SyncPlugin.js";
|
|
16
|
+
import { UndoPlugin } from "../extensions/Collaboration/UndoPlugin.js";
|
|
16
17
|
import { CommentMark } from "../extensions/Comments/CommentMark.js";
|
|
17
18
|
import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js";
|
|
18
|
-
import type { ThreadStore } from "../comments/index.js";
|
|
19
19
|
import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
|
|
20
20
|
import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
|
|
21
21
|
import { HardBreak } from "../extensions/HardBreak/HardBreak.js";
|
|
@@ -31,6 +31,11 @@ import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/Previou
|
|
|
31
31
|
import { ShowSelectionPlugin } from "../extensions/ShowSelection/ShowSelectionPlugin.js";
|
|
32
32
|
import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js";
|
|
33
33
|
import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js";
|
|
34
|
+
import {
|
|
35
|
+
SuggestionAddMark,
|
|
36
|
+
SuggestionDeleteMark,
|
|
37
|
+
SuggestionModificationMark,
|
|
38
|
+
} from "../extensions/Suggestions/SuggestionMarks.js";
|
|
34
39
|
import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js";
|
|
35
40
|
import { TextAlignmentExtension } from "../extensions/TextAlignment/TextAlignmentExtension.js";
|
|
36
41
|
import { TextColorExtension } from "../extensions/TextColor/TextColorExtension.js";
|
|
@@ -49,8 +54,9 @@ import {
|
|
|
49
54
|
import type {
|
|
50
55
|
BlockNoteEditor,
|
|
51
56
|
BlockNoteEditorOptions,
|
|
52
|
-
|
|
57
|
+
SupportedExtension,
|
|
53
58
|
} from "./BlockNoteEditor.js";
|
|
59
|
+
import { ForkYDocPlugin } from "../extensions/Collaboration/ForkYDocPlugin.js";
|
|
54
60
|
|
|
55
61
|
type ExtensionOptions<
|
|
56
62
|
BSchema extends BlockSchema,
|
|
@@ -101,7 +107,7 @@ export const getBlockNoteExtensions = <
|
|
|
101
107
|
>(
|
|
102
108
|
opts: ExtensionOptions<BSchema, I, S>,
|
|
103
109
|
) => {
|
|
104
|
-
const ret: Record<string,
|
|
110
|
+
const ret: Record<string, SupportedExtension> = {};
|
|
105
111
|
const tiptapExtensions = getTipTapExtensions(opts);
|
|
106
112
|
|
|
107
113
|
for (const ext of tiptapExtensions) {
|
|
@@ -115,6 +121,10 @@ export const getBlockNoteExtensions = <
|
|
|
115
121
|
if (opts.collaboration.provider?.awareness) {
|
|
116
122
|
ret["yCursorPlugin"] = new CursorPlugin(opts.collaboration);
|
|
117
123
|
}
|
|
124
|
+
ret["forkYDocPlugin"] = new ForkYDocPlugin({
|
|
125
|
+
editor: opts.editor,
|
|
126
|
+
collaboration: opts.collaboration,
|
|
127
|
+
});
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
// Note: this is pretty hardcoded and will break when user provides plugins with same keys.
|
|
@@ -139,14 +149,6 @@ export const getBlockNoteExtensions = <
|
|
|
139
149
|
ret["tableHandles"] = new TableHandlesProsemirrorPlugin(opts.editor as any);
|
|
140
150
|
}
|
|
141
151
|
|
|
142
|
-
ret["dropCursor"] = {
|
|
143
|
-
plugin: opts.dropCursor({
|
|
144
|
-
width: 5,
|
|
145
|
-
color: "#ddeeff",
|
|
146
|
-
editor: opts.editor,
|
|
147
|
-
}),
|
|
148
|
-
};
|
|
149
|
-
|
|
150
152
|
ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin();
|
|
151
153
|
|
|
152
154
|
ret["showSelection"] = new ShowSelectionPlugin(opts.editor);
|
|
@@ -190,6 +192,17 @@ const getTipTapExtensions = <
|
|
|
190
192
|
Gapcursor,
|
|
191
193
|
|
|
192
194
|
// DropCursor,
|
|
195
|
+
Extension.create({
|
|
196
|
+
name: "dropCursor",
|
|
197
|
+
addProseMirrorPlugins: () => [
|
|
198
|
+
opts.dropCursor({
|
|
199
|
+
width: 5,
|
|
200
|
+
color: "#ddeeff",
|
|
201
|
+
editor: opts.editor,
|
|
202
|
+
}),
|
|
203
|
+
],
|
|
204
|
+
}),
|
|
205
|
+
|
|
193
206
|
UniqueID.configure({
|
|
194
207
|
// everything from bnBlock group (nodes that represent a BlockNote block should have an id)
|
|
195
208
|
types: ["blockContainer", "columnList", "column"],
|
|
@@ -202,6 +215,9 @@ const getTipTapExtensions = <
|
|
|
202
215
|
Text,
|
|
203
216
|
|
|
204
217
|
// marks:
|
|
218
|
+
SuggestionAddMark,
|
|
219
|
+
SuggestionDeleteMark,
|
|
220
|
+
SuggestionModificationMark,
|
|
205
221
|
Link.extend({
|
|
206
222
|
inclusive: false,
|
|
207
223
|
}).configure({
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Editor,
|
|
3
|
+
EditorOptions,
|
|
4
|
+
Editor as TiptapEditor,
|
|
5
|
+
createDocument,
|
|
6
|
+
} from "@tiptap/core";
|
|
3
7
|
|
|
4
8
|
import { Node } from "@tiptap/pm/model";
|
|
5
9
|
|
|
@@ -141,6 +145,10 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
|
|
|
141
145
|
if (!this.view) {
|
|
142
146
|
// before view has been initialized
|
|
143
147
|
this._state = this.state.apply(transaction);
|
|
148
|
+
this.emit("transaction", {
|
|
149
|
+
editor: this,
|
|
150
|
+
transaction,
|
|
151
|
+
});
|
|
144
152
|
return;
|
|
145
153
|
}
|
|
146
154
|
// This is a verbatim copy of the default dispatch method, but with the following changes:
|
|
@@ -216,6 +224,19 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
|
|
|
216
224
|
});
|
|
217
225
|
}
|
|
218
226
|
|
|
227
|
+
// a helper method that can enable plugins before the view has been initialized
|
|
228
|
+
// currently only used for testing
|
|
229
|
+
forceEnablePlugins() {
|
|
230
|
+
if (this.view) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
"forcePluginsEnabled called after view has been initialized",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
this._state = this.state.reconfigure({
|
|
236
|
+
plugins: this.extensionManager.plugins,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
219
240
|
/**
|
|
220
241
|
* Replace the default `createView` method with a custom one - which we call on mount
|
|
221
242
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Plugin } from "prosemirror-state";
|
|
2
1
|
import { defaultSelectionBuilder, yCursorPlugin } from "y-prosemirror";
|
|
3
2
|
import { Awareness } from "y-protocols/awareness.js";
|
|
4
3
|
import * as Y from "yjs";
|
|
4
|
+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
|
|
5
5
|
|
|
6
6
|
export type CollaborationUser = {
|
|
7
7
|
name: string;
|
|
@@ -9,8 +9,11 @@ export type CollaborationUser = {
|
|
|
9
9
|
[key: string]: string;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export class CursorPlugin {
|
|
13
|
-
public
|
|
12
|
+
export class CursorPlugin extends BlockNoteExtension {
|
|
13
|
+
public static key() {
|
|
14
|
+
return "yCursorPlugin";
|
|
15
|
+
}
|
|
16
|
+
|
|
14
17
|
private provider: { awareness: Awareness };
|
|
15
18
|
private recentlyUpdatedCursors: Map<
|
|
16
19
|
number,
|
|
@@ -25,6 +28,7 @@ export class CursorPlugin {
|
|
|
25
28
|
showCursorLabels?: "always" | "activity";
|
|
26
29
|
},
|
|
27
30
|
) {
|
|
31
|
+
super();
|
|
28
32
|
this.provider = collaboration.provider;
|
|
29
33
|
this.recentlyUpdatedCursors = new Map();
|
|
30
34
|
|
|
@@ -62,10 +66,12 @@ export class CursorPlugin {
|
|
|
62
66
|
);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
this.addProsemirrorPlugin(
|
|
70
|
+
yCursorPlugin(this.provider.awareness, {
|
|
71
|
+
selectionBuilder: defaultSelectionBuilder,
|
|
72
|
+
cursorBuilder: this.renderCursor,
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
public get priority() {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
import { Awareness } from "y-protocols/awareness";
|
|
4
|
+
import { BlockNoteEditor } from "../../index.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @vitest-environment jsdom
|
|
8
|
+
*/
|
|
9
|
+
it("can fork a document", async () => {
|
|
10
|
+
const doc = new Y.Doc();
|
|
11
|
+
const fragment = doc.getXmlFragment("doc");
|
|
12
|
+
const editor = BlockNoteEditor.create({
|
|
13
|
+
collaboration: {
|
|
14
|
+
fragment,
|
|
15
|
+
user: { name: "Hello", color: "#FFFFFF" },
|
|
16
|
+
provider: {
|
|
17
|
+
awareness: new Awareness(doc),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const div = document.createElement("div");
|
|
23
|
+
editor.mount(div);
|
|
24
|
+
|
|
25
|
+
editor.replaceBlocks(editor.document, [
|
|
26
|
+
{
|
|
27
|
+
type: "paragraph",
|
|
28
|
+
content: [{ text: "Hello", styles: {}, type: "text" }],
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
33
|
+
"__snapshots__/fork-yjs-snap.html",
|
|
34
|
+
);
|
|
35
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
36
|
+
"__snapshots__/fork-yjs-snap-editor.json",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
editor.forkYDocPlugin!.fork();
|
|
40
|
+
|
|
41
|
+
editor.replaceBlocks(editor.document, [
|
|
42
|
+
{
|
|
43
|
+
type: "paragraph",
|
|
44
|
+
content: [{ text: "Hello World", styles: {}, type: "text" }],
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
49
|
+
"__snapshots__/fork-yjs-snap.html",
|
|
50
|
+
);
|
|
51
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
52
|
+
"__snapshots__/fork-yjs-snap-editor-forked.json",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("can merge a document", async () => {
|
|
57
|
+
const doc = new Y.Doc();
|
|
58
|
+
const fragment = doc.getXmlFragment("doc");
|
|
59
|
+
const editor = BlockNoteEditor.create({
|
|
60
|
+
collaboration: {
|
|
61
|
+
fragment,
|
|
62
|
+
user: { name: "Hello", color: "#FFFFFF" },
|
|
63
|
+
provider: {
|
|
64
|
+
awareness: new Awareness(doc),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const div = document.createElement("div");
|
|
70
|
+
editor.mount(div);
|
|
71
|
+
|
|
72
|
+
editor.replaceBlocks(editor.document, [
|
|
73
|
+
{
|
|
74
|
+
type: "paragraph",
|
|
75
|
+
content: [{ text: "Hello", styles: {}, type: "text" }],
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
80
|
+
"__snapshots__/fork-yjs-snap.html",
|
|
81
|
+
);
|
|
82
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
83
|
+
"__snapshots__/fork-yjs-snap-editor.json",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
editor.forkYDocPlugin!.fork();
|
|
87
|
+
|
|
88
|
+
editor.replaceBlocks(editor.document, [
|
|
89
|
+
{
|
|
90
|
+
type: "paragraph",
|
|
91
|
+
content: [{ text: "Hello World", styles: {}, type: "text" }],
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
96
|
+
"__snapshots__/fork-yjs-snap.html",
|
|
97
|
+
);
|
|
98
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
99
|
+
"__snapshots__/fork-yjs-snap-editor-forked.json",
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
editor.forkYDocPlugin!.merge({ keepChanges: false });
|
|
103
|
+
|
|
104
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
105
|
+
"__snapshots__/fork-yjs-snap.html",
|
|
106
|
+
);
|
|
107
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
108
|
+
"__snapshots__/fork-yjs-snap-editor.json",
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("can fork an keep the changes to the original document", async () => {
|
|
113
|
+
const doc = new Y.Doc();
|
|
114
|
+
const fragment = doc.getXmlFragment("doc");
|
|
115
|
+
const editor = BlockNoteEditor.create({
|
|
116
|
+
collaboration: {
|
|
117
|
+
fragment,
|
|
118
|
+
user: { name: "Hello", color: "#FFFFFF" },
|
|
119
|
+
provider: {
|
|
120
|
+
awareness: new Awareness(doc),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const div = document.createElement("div");
|
|
126
|
+
editor.mount(div);
|
|
127
|
+
|
|
128
|
+
editor.replaceBlocks(editor.document, [
|
|
129
|
+
{
|
|
130
|
+
type: "paragraph",
|
|
131
|
+
content: [{ text: "Hello", styles: {}, type: "text" }],
|
|
132
|
+
},
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
136
|
+
"__snapshots__/fork-yjs-snap.html",
|
|
137
|
+
);
|
|
138
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
139
|
+
"__snapshots__/fork-yjs-snap-editor.json",
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
editor.forkYDocPlugin!.fork();
|
|
143
|
+
|
|
144
|
+
editor.replaceBlocks(editor.document, [
|
|
145
|
+
{
|
|
146
|
+
type: "paragraph",
|
|
147
|
+
content: [{ text: "Hello World", styles: {}, type: "text" }],
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
152
|
+
"__snapshots__/fork-yjs-snap.html",
|
|
153
|
+
);
|
|
154
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
155
|
+
"__snapshots__/fork-yjs-snap-editor-forked.json",
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
editor.forkYDocPlugin!.merge({ keepChanges: true });
|
|
159
|
+
|
|
160
|
+
await expect(fragment.toJSON()).toMatchFileSnapshot(
|
|
161
|
+
"__snapshots__/fork-yjs-snap-forked.html",
|
|
162
|
+
);
|
|
163
|
+
await expect(editor.document).toMatchFileSnapshot(
|
|
164
|
+
"__snapshots__/fork-yjs-snap-editor-forked.json",
|
|
165
|
+
);
|
|
166
|
+
});
|