@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
|
@@ -3,13 +3,13 @@ import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
|
|
|
3
3
|
import { EditorView } from "prosemirror-view";
|
|
4
4
|
|
|
5
5
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
6
|
+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
|
|
6
7
|
import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
|
|
7
8
|
import {
|
|
8
9
|
BlockSchema,
|
|
9
10
|
InlineContentSchema,
|
|
10
11
|
StyleSchema,
|
|
11
12
|
} from "../../schema/index.js";
|
|
12
|
-
import { EventEmitter } from "../../util/EventEmitter.js";
|
|
13
13
|
|
|
14
14
|
export type FormattingToolbarState = UiElementPosition;
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ export class FormattingToolbarView implements PluginView {
|
|
|
25
25
|
state: EditorState;
|
|
26
26
|
from: number;
|
|
27
27
|
to: number;
|
|
28
|
-
}) => boolean = ({ state, from, to }) => {
|
|
28
|
+
}) => boolean = ({ view, state, from, to }) => {
|
|
29
29
|
const { doc, selection } = state;
|
|
30
30
|
const { empty } = selection;
|
|
31
31
|
|
|
@@ -43,7 +43,16 @@ export class FormattingToolbarView implements PluginView {
|
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
if (empty || isEmptyTextBlock) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const focusedElement = document.activeElement;
|
|
51
|
+
if (!this.isElementWithinEditorWrapper(focusedElement) && view.editable) {
|
|
52
|
+
// editable editors must have focus for the toolbar to show
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
47
56
|
};
|
|
48
57
|
|
|
49
58
|
constructor(
|
|
@@ -108,8 +117,22 @@ export class FormattingToolbarView implements PluginView {
|
|
|
108
117
|
}
|
|
109
118
|
};
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
isElementWithinEditorWrapper = (element: Node | null) => {
|
|
121
|
+
if (!element) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const editorWrapper = this.pmView.dom.parentElement;
|
|
125
|
+
if (!editorWrapper) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return editorWrapper.contains(element);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
viewMousedownHandler = (e: MouseEvent) => {
|
|
133
|
+
if (!this.isElementWithinEditorWrapper(e.target as Node)) {
|
|
134
|
+
this.preventShow = true;
|
|
135
|
+
}
|
|
113
136
|
};
|
|
114
137
|
|
|
115
138
|
mouseupHandler = () => {
|
|
@@ -172,12 +195,18 @@ export class FormattingToolbarView implements PluginView {
|
|
|
172
195
|
// e.g. the download file button, should still be accessible. Therefore,
|
|
173
196
|
// logic for hiding when the editor is non-editable is handled
|
|
174
197
|
// individually in each button.
|
|
175
|
-
|
|
198
|
+
const nextState = {
|
|
176
199
|
show: true,
|
|
177
200
|
referencePos: this.getSelectionBoundingBox(),
|
|
178
201
|
};
|
|
179
202
|
|
|
180
|
-
|
|
203
|
+
if (
|
|
204
|
+
nextState.show !== this.state?.show ||
|
|
205
|
+
nextState.referencePos.toJSON() !== this.state?.referencePos.toJSON()
|
|
206
|
+
) {
|
|
207
|
+
this.state = nextState;
|
|
208
|
+
this.emitUpdate();
|
|
209
|
+
}
|
|
181
210
|
|
|
182
211
|
return;
|
|
183
212
|
}
|
|
@@ -236,30 +265,35 @@ export const formattingToolbarPluginKey = new PluginKey(
|
|
|
236
265
|
"FormattingToolbarPlugin",
|
|
237
266
|
);
|
|
238
267
|
|
|
239
|
-
export class FormattingToolbarProsemirrorPlugin extends
|
|
268
|
+
export class FormattingToolbarProsemirrorPlugin extends BlockNoteExtension {
|
|
269
|
+
public static key() {
|
|
270
|
+
return "formattingToolbar";
|
|
271
|
+
}
|
|
272
|
+
|
|
240
273
|
private view: FormattingToolbarView | undefined;
|
|
241
|
-
public readonly plugin: Plugin;
|
|
242
274
|
|
|
243
275
|
constructor(editor: BlockNoteEditor<any, any, any>) {
|
|
244
276
|
super();
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
props: {
|
|
254
|
-
handleKeyDown: (_view, event: KeyboardEvent) => {
|
|
255
|
-
if (event.key === "Escape" && this.shown) {
|
|
256
|
-
this.view!.closeMenu();
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
return false;
|
|
277
|
+
this.addProsemirrorPlugin(
|
|
278
|
+
new Plugin({
|
|
279
|
+
key: formattingToolbarPluginKey,
|
|
280
|
+
view: (editorView) => {
|
|
281
|
+
this.view = new FormattingToolbarView(editor, editorView, (state) => {
|
|
282
|
+
this.emit("update", state);
|
|
283
|
+
});
|
|
284
|
+
return this.view;
|
|
260
285
|
},
|
|
261
|
-
|
|
262
|
-
|
|
286
|
+
props: {
|
|
287
|
+
handleKeyDown: (_view, event: KeyboardEvent) => {
|
|
288
|
+
if (event.key === "Escape" && this.shown) {
|
|
289
|
+
this.view!.closeMenu();
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
263
297
|
}
|
|
264
298
|
|
|
265
299
|
public get shown() {
|
|
@@ -4,15 +4,15 @@ import { EditorView } from "@tiptap/pm/view";
|
|
|
4
4
|
import { Mark } from "prosemirror-model";
|
|
5
5
|
import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
|
|
6
6
|
|
|
7
|
+
import { getPmSchema } from "../../api/pmUtil.js";
|
|
7
8
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
9
|
+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
|
|
8
10
|
import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
|
|
9
11
|
import {
|
|
10
12
|
BlockSchema,
|
|
11
13
|
InlineContentSchema,
|
|
12
14
|
StyleSchema,
|
|
13
15
|
} from "../../schema/index.js";
|
|
14
|
-
import { EventEmitter } from "../../util/EventEmitter.js";
|
|
15
|
-
import { getPmSchema } from "../../api/pmUtil.js";
|
|
16
16
|
|
|
17
17
|
export type LinkToolbarState = UiElementPosition & {
|
|
18
18
|
// The hovered link's URL, and the text it's displayed with in the
|
|
@@ -301,30 +301,35 @@ export class LinkToolbarProsemirrorPlugin<
|
|
|
301
301
|
BSchema extends BlockSchema,
|
|
302
302
|
I extends InlineContentSchema,
|
|
303
303
|
S extends StyleSchema,
|
|
304
|
-
> extends
|
|
304
|
+
> extends BlockNoteExtension {
|
|
305
|
+
public static key() {
|
|
306
|
+
return "linkToolbar";
|
|
307
|
+
}
|
|
308
|
+
|
|
305
309
|
private view: LinkToolbarView | undefined;
|
|
306
|
-
public readonly plugin: Plugin;
|
|
307
310
|
|
|
308
311
|
constructor(editor: BlockNoteEditor<BSchema, I, S>) {
|
|
309
312
|
super();
|
|
310
|
-
this.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
props: {
|
|
319
|
-
handleKeyDown: (_view, event: KeyboardEvent) => {
|
|
320
|
-
if (event.key === "Escape" && this.shown) {
|
|
321
|
-
this.view!.closeMenu();
|
|
322
|
-
return true;
|
|
323
|
-
}
|
|
324
|
-
return false;
|
|
313
|
+
this.addProsemirrorPlugin(
|
|
314
|
+
new Plugin({
|
|
315
|
+
key: linkToolbarPluginKey,
|
|
316
|
+
view: (editorView) => {
|
|
317
|
+
this.view = new LinkToolbarView(editor, editorView, (state) => {
|
|
318
|
+
this.emit("update", state);
|
|
319
|
+
});
|
|
320
|
+
return this.view;
|
|
325
321
|
},
|
|
326
|
-
|
|
327
|
-
|
|
322
|
+
props: {
|
|
323
|
+
handleKeyDown: (_view, event: KeyboardEvent) => {
|
|
324
|
+
if (event.key === "Escape" && this.shown) {
|
|
325
|
+
this.view!.closeMenu();
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
328
333
|
}
|
|
329
334
|
|
|
330
335
|
public onUpdate(callback: (state: LinkToolbarState) => void) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Plugin, PluginKey, TextSelection } from "prosemirror-state";
|
|
2
|
+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
|
|
2
3
|
|
|
3
4
|
const PLUGIN_KEY = new PluginKey("node-selection-keyboard");
|
|
4
5
|
// By default, typing with a node selection active will cause ProseMirror to
|
|
@@ -15,54 +16,60 @@ const PLUGIN_KEY = new PluginKey("node-selection-keyboard");
|
|
|
15
16
|
// While a more elegant solution would probably process transactions instead of
|
|
16
17
|
// keystrokes, this brings us most of the way to Notion's UX without much added
|
|
17
18
|
// complexity.
|
|
18
|
-
export class NodeSelectionKeyboardPlugin {
|
|
19
|
-
public
|
|
19
|
+
export class NodeSelectionKeyboardPlugin extends BlockNoteExtension {
|
|
20
|
+
public static key() {
|
|
21
|
+
return "nodeSelectionKeyboard";
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
constructor() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Checks
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
super();
|
|
26
|
+
this.addProsemirrorPlugin(
|
|
27
|
+
new Plugin({
|
|
28
|
+
key: PLUGIN_KEY,
|
|
29
|
+
props: {
|
|
30
|
+
handleKeyDown: (view, event) => {
|
|
31
|
+
// Checks for node selection
|
|
32
|
+
if ("node" in view.state.selection) {
|
|
33
|
+
// Checks if key press uses ctrl/meta modifier
|
|
34
|
+
if (event.ctrlKey || event.metaKey) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Checks if key press is alphanumeric
|
|
38
|
+
if (event.key.length === 1) {
|
|
39
|
+
event.preventDefault();
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Checks if key press is Enter
|
|
44
|
+
if (
|
|
45
|
+
event.key === "Enter" &&
|
|
46
|
+
!event.shiftKey &&
|
|
47
|
+
!event.altKey &&
|
|
48
|
+
!event.ctrlKey &&
|
|
49
|
+
!event.metaKey
|
|
50
|
+
) {
|
|
51
|
+
const tr = view.state.tr;
|
|
52
|
+
view.dispatch(
|
|
53
|
+
tr
|
|
54
|
+
.insert(
|
|
55
|
+
view.state.tr.selection.$to.after(),
|
|
56
|
+
view.state.schema.nodes["paragraph"].createChecked(),
|
|
57
|
+
)
|
|
58
|
+
.setSelection(
|
|
59
|
+
new TextSelection(
|
|
60
|
+
tr.doc.resolve(view.state.tr.selection.$to.after() + 1),
|
|
61
|
+
),
|
|
55
62
|
),
|
|
56
|
-
|
|
57
|
-
);
|
|
63
|
+
);
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
60
67
|
}
|
|
61
|
-
}
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
return false;
|
|
70
|
+
},
|
|
64
71
|
},
|
|
65
|
-
},
|
|
66
|
-
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
67
74
|
}
|
|
68
75
|
}
|
|
@@ -2,11 +2,15 @@ import { Plugin, PluginKey } from "prosemirror-state";
|
|
|
2
2
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
5
|
+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
|
|
5
6
|
|
|
6
7
|
const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
|
|
7
8
|
|
|
8
|
-
export class PlaceholderPlugin {
|
|
9
|
-
public
|
|
9
|
+
export class PlaceholderPlugin extends BlockNoteExtension {
|
|
10
|
+
public static key() {
|
|
11
|
+
return "placeholder";
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
constructor(
|
|
11
15
|
editor: BlockNoteEditor<any, any, any>,
|
|
12
16
|
placeholders: Record<
|
|
@@ -14,127 +18,130 @@ export class PlaceholderPlugin {
|
|
|
14
18
|
string | undefined
|
|
15
19
|
>,
|
|
16
20
|
) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
view
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
21
|
+
super();
|
|
22
|
+
this.addProsemirrorPlugin(
|
|
23
|
+
new Plugin({
|
|
24
|
+
key: PLUGIN_KEY,
|
|
25
|
+
view: (view) => {
|
|
26
|
+
const uniqueEditorSelector = `placeholder-selector-${v4()}`;
|
|
27
|
+
view.dom.classList.add(uniqueEditorSelector);
|
|
28
|
+
const styleEl = document.createElement("style");
|
|
29
|
+
|
|
30
|
+
const nonce = editor._tiptapEditor.options.injectNonce;
|
|
31
|
+
if (nonce) {
|
|
32
|
+
styleEl.setAttribute("nonce", nonce);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (editor.prosemirrorView?.root instanceof ShadowRoot) {
|
|
36
|
+
editor.prosemirrorView.root.append(styleEl);
|
|
37
|
+
} else {
|
|
38
|
+
editor.prosemirrorView?.root.head.appendChild(styleEl);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const styleSheet = styleEl.sheet!;
|
|
42
|
+
|
|
43
|
+
const getSelector = (additionalSelectors = "") =>
|
|
44
|
+
`.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// FIXME: the names "default" and "emptyDocument" are hardcoded
|
|
48
|
+
const {
|
|
49
|
+
default: defaultPlaceholder,
|
|
50
|
+
emptyDocument: emptyPlaceholder,
|
|
51
|
+
...rest
|
|
52
|
+
} = placeholders;
|
|
53
|
+
|
|
54
|
+
// add block specific placeholders
|
|
55
|
+
for (const [blockType, placeholder] of Object.entries(rest)) {
|
|
56
|
+
const blockTypeSelector = `[data-content-type="${blockType}"]`;
|
|
57
|
+
|
|
58
|
+
styleSheet.insertRule(
|
|
59
|
+
`${getSelector(blockTypeSelector)} { content: ${JSON.stringify(
|
|
60
|
+
placeholder,
|
|
61
|
+
)}; }`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const onlyBlockSelector = `[data-is-only-empty-block]`;
|
|
66
|
+
const mustBeFocusedSelector = `[data-is-empty-and-focused]`;
|
|
67
|
+
|
|
68
|
+
// placeholder for when there's only one empty block
|
|
69
|
+
styleSheet.insertRule(
|
|
70
|
+
`${getSelector(onlyBlockSelector)} { content: ${JSON.stringify(
|
|
71
|
+
emptyPlaceholder,
|
|
72
|
+
)}; }`,
|
|
73
|
+
);
|
|
51
74
|
|
|
75
|
+
// placeholder for default blocks, only when the cursor is in the block (mustBeFocused)
|
|
52
76
|
styleSheet.insertRule(
|
|
53
|
-
`${getSelector(
|
|
54
|
-
|
|
77
|
+
`${getSelector(mustBeFocusedSelector)} { content: ${JSON.stringify(
|
|
78
|
+
defaultPlaceholder,
|
|
55
79
|
)}; }`,
|
|
56
80
|
);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.warn(
|
|
84
|
+
`Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`,
|
|
85
|
+
e,
|
|
86
|
+
);
|
|
57
87
|
}
|
|
58
88
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
} catch (e) {
|
|
76
|
-
// eslint-disable-next-line no-console
|
|
77
|
-
console.warn(
|
|
78
|
-
`Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`,
|
|
79
|
-
e,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
destroy: () => {
|
|
85
|
-
if (editor.prosemirrorView?.root instanceof ShadowRoot) {
|
|
86
|
-
editor.prosemirrorView.root.removeChild(styleEl);
|
|
87
|
-
} else {
|
|
88
|
-
editor.prosemirrorView?.root.head.removeChild(styleEl);
|
|
89
|
+
return {
|
|
90
|
+
destroy: () => {
|
|
91
|
+
if (editor.prosemirrorView?.root instanceof ShadowRoot) {
|
|
92
|
+
editor.prosemirrorView.root.removeChild(styleEl);
|
|
93
|
+
} else {
|
|
94
|
+
editor.prosemirrorView?.root.head.removeChild(styleEl);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
props: {
|
|
100
|
+
decorations: (state) => {
|
|
101
|
+
const { doc, selection } = state;
|
|
102
|
+
|
|
103
|
+
if (!editor.isEditable) {
|
|
104
|
+
return;
|
|
89
105
|
}
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
props: {
|
|
94
|
-
decorations: (state) => {
|
|
95
|
-
const { doc, selection } = state;
|
|
96
|
-
|
|
97
|
-
if (!editor.isEditable) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
if (!selection.empty) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
// Don't show placeholder when the cursor is inside a code block
|
|
112
|
+
if (selection.$from.parent.type.spec.code) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
const decs = [];
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// decoration for when there's only one empty block
|
|
119
|
+
// positions are hardcoded for now
|
|
120
|
+
if (state.doc.content.size === 6) {
|
|
121
|
+
decs.push(
|
|
122
|
+
Decoration.node(2, 4, {
|
|
123
|
+
"data-is-only-empty-block": "true",
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
121
127
|
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
const $pos = selection.$anchor;
|
|
129
|
+
const node = $pos.parent;
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
if (node.content.size === 0) {
|
|
132
|
+
const before = $pos.before();
|
|
127
133
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
decs.push(
|
|
135
|
+
Decoration.node(before, before + node.nodeSize, {
|
|
136
|
+
"data-is-empty-and-focused": "true",
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
134
140
|
|
|
135
|
-
|
|
141
|
+
return DecorationSet.create(doc, decs);
|
|
142
|
+
},
|
|
136
143
|
},
|
|
137
|
-
},
|
|
138
|
-
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
139
146
|
}
|
|
140
147
|
}
|