@blocknote/core 0.15.7 → 0.15.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocknote.js +1304 -1194
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +5 -5
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +23 -23
- package/src/api/exporters/copyExtension.ts +48 -33
- package/src/api/exporters/html/__snapshots__/complex/misc/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/customParagraph/styled/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/file/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/file/nested/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/file/noCaption/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/file/noName/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/fontSize/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/between-links/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/end/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/link/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/multiple/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/only/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/start/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/hardbreak/styles/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/image/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/image/nested/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/image/noCaption/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/image/noName/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/image/noPreview/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/link/adjacent/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/link/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/link/styled/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/mention/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/paragraph/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/paragraph/empty/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/paragraph/lineBreaks/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/paragraph/nested/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/paragraph/styled/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleCustomParagraph/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleCustomParagraph/nested/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleCustomParagraph/styled/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleImage/button/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/small/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots__/tag/basic/external.html +1 -1
- package/src/api/exporters/html/__snapshots_fragment_edge_cases__/selectionLeavesBlockChildren.html +1 -1
- package/src/api/exporters/html/__snapshots_fragment_edge_cases__/selectionSpansBlocksChildren.html +1 -1
- package/src/api/exporters/html/__snapshots_fragment_edge_cases__/selectionWithinBlockChildren.html +1 -1
- package/src/api/exporters/html/util/simplifyBlocksRehypePlugin.ts +51 -2
- package/src/api/parsers/handleFileInsertion.ts +30 -17
- package/src/blocks/AudioBlockContent/AudioBlockContent.ts +23 -47
- package/src/blocks/FileBlockContent/FileBlockContent.ts +4 -22
- package/src/blocks/FileBlockContent/fileBlockHelpers.ts +72 -1
- package/src/blocks/ImageBlockContent/ImageBlockContent.ts +35 -61
- package/src/blocks/VideoBlockContent/VideoBlockContent.ts +33 -58
- package/src/editor/BlockNoteEditor.test.ts +13 -0
- package/src/editor/BlockNoteEditor.ts +71 -6
- package/src/editor/BlockNoteExtensions.ts +4 -2
- package/src/editor/BlockNoteTipTapEditor.ts +4 -1
- package/src/extensions/FilePanel/FilePanelPlugin.ts +10 -6
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +0 -1
- package/src/extensions/SideMenu/SideMenuPlugin.ts +22 -11
- package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +6 -3
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +5 -1
- package/src/extensions/UniqueID/UniqueID.ts +15 -4
- package/src/pm-nodes/BlockContainer.ts +1 -2
- package/src/schema/blocks/createSpec.ts +31 -2
- package/src/schema/blocks/types.ts +2 -0
- package/src/schema/inlineContent/createSpec.ts +58 -6
- package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +2 -2
- package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +2 -2
- package/types/src/blocks/FileBlockContent/fileBlockHelpers.d.ts +11 -1
- package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +2 -2
- package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +2 -2
- package/types/src/editor/BlockNoteEditor.d.ts +23 -4
- package/types/src/editor/BlockNoteExtensions.d.ts +1 -0
- package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +1 -1
- package/types/src/schema/blocks/createSpec.d.ts +3 -0
- package/types/src/schema/blocks/types.d.ts +2 -0
- package/types/src/schema/inlineContent/createSpec.d.ts +5 -4
|
@@ -12,8 +12,8 @@ import { Link } from "@tiptap/extension-link";
|
|
|
12
12
|
import { Text } from "@tiptap/extension-text";
|
|
13
13
|
import * as Y from "yjs";
|
|
14
14
|
import { createCopyToClipboardExtension } from "../api/exporters/copyExtension";
|
|
15
|
-
import { createPasteFromClipboardExtension } from "../api/parsers/pasteExtension";
|
|
16
15
|
import { createDropFileExtension } from "../api/parsers/fileDropExtension";
|
|
16
|
+
import { createPasteFromClipboardExtension } from "../api/parsers/pasteExtension";
|
|
17
17
|
import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension";
|
|
18
18
|
import { TextAlignmentExtension } from "../extensions/TextAlignment/TextAlignmentExtension";
|
|
19
19
|
import { TextColorExtension } from "../extensions/TextColor/TextColorExtension";
|
|
@@ -55,6 +55,7 @@ export const getBlockNoteExtensions = <
|
|
|
55
55
|
renderCursor?: (user: any) => HTMLElement;
|
|
56
56
|
};
|
|
57
57
|
disableExtensions: string[] | undefined;
|
|
58
|
+
setIdAttribute?: boolean;
|
|
58
59
|
}) => {
|
|
59
60
|
const ret: Extensions = [
|
|
60
61
|
extensions.ClipboardTextSerializer,
|
|
@@ -69,6 +70,7 @@ export const getBlockNoteExtensions = <
|
|
|
69
70
|
// DropCursor,
|
|
70
71
|
UniqueID.configure({
|
|
71
72
|
types: ["blockContainer"],
|
|
73
|
+
setIdAttribute: opts.setIdAttribute,
|
|
72
74
|
}),
|
|
73
75
|
HardBreak.extend({ priority: 10 }),
|
|
74
76
|
// Comments,
|
|
@@ -197,5 +199,5 @@ export const getBlockNoteExtensions = <
|
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
const disableExtensions: string[] = opts.disableExtensions || [];
|
|
200
|
-
return ret.filter(ex => !disableExtensions.includes(ex.name));
|
|
202
|
+
return ret.filter((ex) => !disableExtensions.includes(ex.name));
|
|
201
203
|
};
|
|
@@ -168,8 +168,11 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
|
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
(BlockNoteTipTapEditor.prototype as any).createView = ()
|
|
171
|
+
(BlockNoteTipTapEditor.prototype as any).createView = function () {
|
|
172
172
|
// no-op
|
|
173
173
|
// Disable default call to `createView` in the Editor constructor.
|
|
174
174
|
// We should call `createView` manually only when a DOM element is available
|
|
175
|
+
|
|
176
|
+
// additional fix because onPaste and onDrop depend on installing plugins in constructor which we don't support
|
|
177
|
+
this.options.onPaste = this.options.onDrop = undefined;
|
|
175
178
|
};
|
|
@@ -71,8 +71,10 @@ export class FilePanelView<I extends InlineContentSchema, S extends StyleSchema>
|
|
|
71
71
|
if (this.state?.show) {
|
|
72
72
|
const blockElement = this.pmView.root.querySelector(
|
|
73
73
|
`[data-node-type="blockContainer"][data-id="${this.state.block.id}"]`
|
|
74
|
-
)
|
|
75
|
-
|
|
74
|
+
);
|
|
75
|
+
if (!blockElement) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
76
78
|
this.state.referencePos = blockElement.getBoundingClientRect();
|
|
77
79
|
this.emitUpdate();
|
|
78
80
|
}
|
|
@@ -86,8 +88,10 @@ export class FilePanelView<I extends InlineContentSchema, S extends StyleSchema>
|
|
|
86
88
|
if (!this.state?.show && pluginState.block && this.editor.isEditable) {
|
|
87
89
|
const blockElement = this.pmView.root.querySelector(
|
|
88
90
|
`[data-node-type="blockContainer"][data-id="${pluginState.block.id}"]`
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
+
);
|
|
92
|
+
if (!blockElement) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
91
95
|
this.state = {
|
|
92
96
|
show: true,
|
|
93
97
|
referencePos: blockElement.getBoundingClientRect(),
|
|
@@ -157,7 +161,7 @@ export class FilePanelProsemirrorPlugin<
|
|
|
157
161
|
props: {
|
|
158
162
|
handleKeyDown: (_view, event: KeyboardEvent) => {
|
|
159
163
|
if (event.key === "Escape" && this.shown) {
|
|
160
|
-
this.view
|
|
164
|
+
this.view?.closeMenu();
|
|
161
165
|
return true;
|
|
162
166
|
}
|
|
163
167
|
return false;
|
|
@@ -189,5 +193,5 @@ export class FilePanelProsemirrorPlugin<
|
|
|
189
193
|
return this.on("update", callback);
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
public closeMenu = () => this.view
|
|
196
|
+
public closeMenu = () => this.view?.closeMenu();
|
|
193
197
|
}
|
|
@@ -267,7 +267,7 @@ export class SideMenuView<
|
|
|
267
267
|
// When false, the drag handle with be just to the left of the element
|
|
268
268
|
// TODO: Is there any case where we want this to be false?
|
|
269
269
|
private horizontalPosAnchoredAtRoot: boolean;
|
|
270
|
-
private horizontalPosAnchor: number;
|
|
270
|
+
private horizontalPosAnchor: number | undefined;
|
|
271
271
|
|
|
272
272
|
private hoveredBlock: HTMLElement | undefined;
|
|
273
273
|
|
|
@@ -290,9 +290,12 @@ export class SideMenuView<
|
|
|
290
290
|
};
|
|
291
291
|
|
|
292
292
|
this.horizontalPosAnchoredAtRoot = true;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
|
|
294
|
+
if (this.pmView.dom.firstChild) {
|
|
295
|
+
this.horizontalPosAnchor = (
|
|
296
|
+
this.pmView.dom.firstChild as HTMLElement
|
|
297
|
+
).getBoundingClientRect().x;
|
|
298
|
+
}
|
|
296
299
|
|
|
297
300
|
this.pmView.root.addEventListener(
|
|
298
301
|
"drop",
|
|
@@ -337,8 +340,12 @@ export class SideMenuView<
|
|
|
337
340
|
// size/position, so we get the boundingRect of the first child (i.e. the
|
|
338
341
|
// blockGroup that wraps all blocks in the editor) for more accurate side
|
|
339
342
|
// menu placement.
|
|
343
|
+
if (!this.pmView.dom.firstChild) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
340
347
|
const editorBoundingBox = (
|
|
341
|
-
this.pmView.dom.firstChild
|
|
348
|
+
this.pmView.dom.firstChild as HTMLElement
|
|
342
349
|
).getBoundingClientRect();
|
|
343
350
|
|
|
344
351
|
this.horizontalPosAnchor = editorBoundingBox.x;
|
|
@@ -441,7 +448,7 @@ export class SideMenuView<
|
|
|
441
448
|
if (!pos || pos.inside === -1) {
|
|
442
449
|
const evt = new Event("drop", event) as any;
|
|
443
450
|
const editorBoundingBox = (
|
|
444
|
-
this.pmView.dom.firstChild
|
|
451
|
+
this.pmView.dom.firstChild as HTMLElement
|
|
445
452
|
).getBoundingClientRect();
|
|
446
453
|
evt.clientX =
|
|
447
454
|
event.clientX < editorBoundingBox.left ||
|
|
@@ -474,10 +481,10 @@ export class SideMenuView<
|
|
|
474
481
|
top: event.clientY,
|
|
475
482
|
});
|
|
476
483
|
|
|
477
|
-
if (!pos || pos.inside === -1) {
|
|
484
|
+
if (!pos || (pos.inside === -1 && this.pmView.dom.firstChild)) {
|
|
478
485
|
const evt = new Event("dragover", event) as any;
|
|
479
486
|
const editorBoundingBox = (
|
|
480
|
-
this.pmView.dom.firstChild
|
|
487
|
+
this.pmView.dom.firstChild as HTMLElement
|
|
481
488
|
).getBoundingClientRect();
|
|
482
489
|
evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
|
|
483
490
|
evt.clientY = event.clientY;
|
|
@@ -555,8 +562,8 @@ export class SideMenuView<
|
|
|
555
562
|
};
|
|
556
563
|
|
|
557
564
|
onScroll = () => {
|
|
558
|
-
if (this.state?.show) {
|
|
559
|
-
const blockContent = this.hoveredBlock
|
|
565
|
+
if (this.state?.show && this.hoveredBlock?.firstChild) {
|
|
566
|
+
const blockContent = this.hoveredBlock.firstChild as HTMLElement;
|
|
560
567
|
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
561
568
|
|
|
562
569
|
this.state.referencePos = new DOMRect(
|
|
@@ -624,7 +631,11 @@ export class SideMenuView<
|
|
|
624
631
|
this.emitUpdate(this.state);
|
|
625
632
|
}
|
|
626
633
|
|
|
627
|
-
|
|
634
|
+
if (!this.hoveredBlock?.firstChild) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const blockContent = this.hoveredBlock.firstChild as HTMLElement;
|
|
628
639
|
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
629
640
|
|
|
630
641
|
const pos = this.pmView.posAtCoords({
|
|
@@ -54,7 +54,10 @@ class SuggestionMenuView<
|
|
|
54
54
|
const decorationNode = this.rootEl?.querySelector(
|
|
55
55
|
`[data-decoration-id="${this.pluginState!.decorationId}"]`
|
|
56
56
|
);
|
|
57
|
-
|
|
57
|
+
if (!decorationNode) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.state.referencePos = decorationNode.getBoundingClientRect();
|
|
58
61
|
this.emitUpdate(this.pluginState!.triggerCharacter!);
|
|
59
62
|
}
|
|
60
63
|
};
|
|
@@ -89,10 +92,10 @@ class SuggestionMenuView<
|
|
|
89
92
|
`[data-decoration-id="${this.pluginState!.decorationId}"]`
|
|
90
93
|
);
|
|
91
94
|
|
|
92
|
-
if (this.editor.isEditable) {
|
|
95
|
+
if (this.editor.isEditable && decorationNode) {
|
|
93
96
|
this.state = {
|
|
94
97
|
show: true,
|
|
95
|
-
referencePos: decorationNode
|
|
98
|
+
referencePos: decorationNode.getBoundingClientRect(),
|
|
96
99
|
query: this.pluginState!.query,
|
|
97
100
|
};
|
|
98
101
|
|
|
@@ -159,7 +159,11 @@ export class TableHandlesView<
|
|
|
159
159
|
const rowIndex = getChildIndex(target.parentElement!);
|
|
160
160
|
const cellRect = target.getBoundingClientRect();
|
|
161
161
|
const tableRect =
|
|
162
|
-
target.parentElement
|
|
162
|
+
target.parentElement?.parentElement?.getBoundingClientRect();
|
|
163
|
+
|
|
164
|
+
if (!tableRect) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
163
167
|
|
|
164
168
|
const blockEl = getDraggableBlockFromElement(target, this.pmView);
|
|
165
169
|
if (!blockEl) {
|
|
@@ -50,6 +50,7 @@ const UniqueID = Extension.create({
|
|
|
50
50
|
return {
|
|
51
51
|
attributeName: "id",
|
|
52
52
|
types: [],
|
|
53
|
+
setIdAttribute: false,
|
|
53
54
|
generateID: () => {
|
|
54
55
|
// Use mock ID if tests are running.
|
|
55
56
|
if (typeof window !== "undefined" && (window as any).__TEST_OPTIONS) {
|
|
@@ -77,10 +78,20 @@ const UniqueID = Extension.create({
|
|
|
77
78
|
default: null,
|
|
78
79
|
parseHTML: (element) =>
|
|
79
80
|
element.getAttribute(`data-${this.options.attributeName}`),
|
|
80
|
-
renderHTML: (attributes) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
renderHTML: (attributes) => {
|
|
82
|
+
const defaultIdAttributes = {
|
|
83
|
+
[`data-${this.options.attributeName}`]:
|
|
84
|
+
attributes[this.options.attributeName],
|
|
85
|
+
};
|
|
86
|
+
if (this.options.setIdAttribute) {
|
|
87
|
+
return {
|
|
88
|
+
...defaultIdAttributes,
|
|
89
|
+
id: attributes[this.options.attributeName],
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
return defaultIdAttributes;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
84
95
|
},
|
|
85
96
|
},
|
|
86
97
|
},
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
import { PartialBlock } from "../blocks/defaultBlocks";
|
|
12
12
|
import type { BlockNoteEditor } from "../editor/BlockNoteEditor";
|
|
13
13
|
import { NonEditableBlockPlugin } from "../extensions/NonEditableBlocks/NonEditableBlockPlugin";
|
|
14
|
-
import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin";
|
|
15
14
|
import {
|
|
16
15
|
BlockNoteDOMAttributes,
|
|
17
16
|
BlockSchema,
|
|
@@ -492,7 +491,7 @@ export const BlockContainer = Node.create<{
|
|
|
492
491
|
},
|
|
493
492
|
|
|
494
493
|
addProseMirrorPlugins() {
|
|
495
|
-
return [
|
|
494
|
+
return [NonEditableBlockPlugin()];
|
|
496
495
|
},
|
|
497
496
|
|
|
498
497
|
addKeyboardShortcuts() {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Editor } from "@tiptap/core";
|
|
1
2
|
import { TagParseRule } from "@tiptap/pm/model";
|
|
3
|
+
import { NodeView } from "@tiptap/pm/view";
|
|
2
4
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
3
5
|
import { InlineContentSchema } from "../inlineContent/types";
|
|
4
6
|
import { StyleSchema } from "../styles/types";
|
|
@@ -61,6 +63,27 @@ export type CustomBlockImplementation<
|
|
|
61
63
|
) => PartialBlockFromConfig<T, I, S>["props"] | undefined;
|
|
62
64
|
};
|
|
63
65
|
|
|
66
|
+
// Function that enables copying of selected content within non-selectable
|
|
67
|
+
// blocks.
|
|
68
|
+
export function applyNonSelectableBlockFix(nodeView: NodeView, editor: Editor) {
|
|
69
|
+
nodeView.stopEvent = (event) => {
|
|
70
|
+
// Ensures copy events are handled by the browser and not by ProseMirror.
|
|
71
|
+
if (event.type === "copy" || event.type === "cut") {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
// Blurs the editor on mouse down as the block is non-selectable. This is
|
|
75
|
+
// mainly done to prevent UI elements like the formatting toolbar from being
|
|
76
|
+
// visible while content within a non-selectable block is selected.
|
|
77
|
+
if (event.type === "mousedown") {
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
editor.view.dom.blur();
|
|
80
|
+
}, 10);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
64
87
|
// Function that uses the 'parse' function of a blockConfig to create a
|
|
65
88
|
// TipTap node's `parseHTML` property. This is only used for parsing content
|
|
66
89
|
// from the clipboard.
|
|
@@ -125,7 +148,7 @@ export function createBlockSpec<
|
|
|
125
148
|
? "inline*"
|
|
126
149
|
: "") as T["content"] extends "inline" ? "inline*" : "",
|
|
127
150
|
group: "blockContent",
|
|
128
|
-
selectable: true,
|
|
151
|
+
selectable: blockConfig.isSelectable ?? true,
|
|
129
152
|
|
|
130
153
|
addAttributes() {
|
|
131
154
|
return propsToAttributes(blockConfig.propSchema);
|
|
@@ -163,13 +186,19 @@ export function createBlockSpec<
|
|
|
163
186
|
|
|
164
187
|
const output = blockImplementation.render(block as any, editor);
|
|
165
188
|
|
|
166
|
-
|
|
189
|
+
const nodeView: NodeView = wrapInBlockStructure(
|
|
167
190
|
output,
|
|
168
191
|
block.type,
|
|
169
192
|
block.props,
|
|
170
193
|
blockConfig.propSchema,
|
|
171
194
|
blockContentDOMAttributes
|
|
172
195
|
);
|
|
196
|
+
|
|
197
|
+
if (blockConfig.isSelectable === false) {
|
|
198
|
+
applyNonSelectableBlockFix(nodeView, this.editor);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return nodeView;
|
|
173
202
|
};
|
|
174
203
|
},
|
|
175
204
|
});
|
|
@@ -49,6 +49,7 @@ export type FileBlockConfig = {
|
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
51
|
content: "none";
|
|
52
|
+
isSelectable?: boolean;
|
|
52
53
|
isFileBlock: true;
|
|
53
54
|
fileBlockAccept?: string[];
|
|
54
55
|
};
|
|
@@ -60,6 +61,7 @@ export type BlockConfig =
|
|
|
60
61
|
type: string;
|
|
61
62
|
readonly propSchema: PropSchema;
|
|
62
63
|
content: "inline" | "none" | "table";
|
|
64
|
+
isSelectable?: boolean;
|
|
63
65
|
isFileBlock?: false;
|
|
64
66
|
}
|
|
65
67
|
| FileBlockConfig;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Node } from "@tiptap/core";
|
|
2
2
|
import { TagParseRule } from "@tiptap/pm/model";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
inlineContentToNodes,
|
|
5
|
+
nodeToCustomInlineContent,
|
|
6
|
+
} from "../../api/nodeConversions/nodeConversions";
|
|
7
|
+
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
4
8
|
import { propsToAttributes } from "../blocks/internal";
|
|
5
9
|
import { Props } from "../propTypes";
|
|
6
10
|
import { StyleSchema } from "../styles/types";
|
|
@@ -11,15 +15,15 @@ import {
|
|
|
11
15
|
} from "./internal";
|
|
12
16
|
import {
|
|
13
17
|
CustomInlineContentConfig,
|
|
14
|
-
InlineContentConfig,
|
|
15
18
|
InlineContentFromConfig,
|
|
16
19
|
InlineContentSpec,
|
|
20
|
+
PartialCustomInlineContentFromConfig,
|
|
17
21
|
} from "./types";
|
|
18
22
|
|
|
19
23
|
// TODO: support serialization
|
|
20
24
|
|
|
21
25
|
export type CustomInlineContentImplementation<
|
|
22
|
-
T extends
|
|
26
|
+
T extends CustomInlineContentConfig,
|
|
23
27
|
// B extends BlockSchema,
|
|
24
28
|
// I extends InlineContentSchema,
|
|
25
29
|
S extends StyleSchema
|
|
@@ -28,13 +32,16 @@ export type CustomInlineContentImplementation<
|
|
|
28
32
|
/**
|
|
29
33
|
* The custom inline content to render
|
|
30
34
|
*/
|
|
31
|
-
inlineContent: InlineContentFromConfig<T, S
|
|
35
|
+
inlineContent: InlineContentFromConfig<T, S>,
|
|
36
|
+
updateInlineContent: (
|
|
37
|
+
update: PartialCustomInlineContentFromConfig<T, S>
|
|
38
|
+
) => void,
|
|
32
39
|
/**
|
|
33
40
|
* The BlockNote editor instance
|
|
34
41
|
* This is typed generically. If you want an editor with your custom schema, you need to
|
|
35
42
|
* cast it manually, e.g.: `const e = editor as BlockNoteEditor<typeof mySchema>;`
|
|
36
43
|
*/
|
|
37
|
-
|
|
44
|
+
editor: BlockNoteEditor<any, any, S>
|
|
38
45
|
// (note) if we want to fix the manual cast, we need to prevent circular references and separate block definition and render implementations
|
|
39
46
|
// or allow manually passing <BSchema>, but that's not possible without passing the other generics because Typescript doesn't support partial inferred generics
|
|
40
47
|
) => {
|
|
@@ -100,7 +107,11 @@ export function createInlineContentSpec<
|
|
|
100
107
|
node,
|
|
101
108
|
editor.schema.inlineContentSchema,
|
|
102
109
|
editor.schema.styleSchema
|
|
103
|
-
) as any as InlineContentFromConfig<T, S
|
|
110
|
+
) as any as InlineContentFromConfig<T, S>, // TODO: fix cast
|
|
111
|
+
() => {
|
|
112
|
+
// No-op
|
|
113
|
+
},
|
|
114
|
+
editor
|
|
104
115
|
);
|
|
105
116
|
|
|
106
117
|
return addInlineContentAttributes(
|
|
@@ -110,6 +121,47 @@ export function createInlineContentSpec<
|
|
|
110
121
|
inlineContentConfig.propSchema
|
|
111
122
|
);
|
|
112
123
|
},
|
|
124
|
+
|
|
125
|
+
addNodeView() {
|
|
126
|
+
return ({ node, getPos }) => {
|
|
127
|
+
const editor = this.options.editor;
|
|
128
|
+
|
|
129
|
+
const output = inlineContentImplementation.render(
|
|
130
|
+
nodeToCustomInlineContent(
|
|
131
|
+
node,
|
|
132
|
+
editor.schema.inlineContentSchema,
|
|
133
|
+
editor.schema.styleSchema
|
|
134
|
+
) as any as InlineContentFromConfig<T, S>, // TODO: fix cast
|
|
135
|
+
(update) => {
|
|
136
|
+
if (typeof getPos === "boolean") {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const content = inlineContentToNodes(
|
|
141
|
+
[update],
|
|
142
|
+
editor._tiptapEditor.schema,
|
|
143
|
+
editor.schema.styleSchema
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
editor._tiptapEditor.view.dispatch(
|
|
147
|
+
editor._tiptapEditor.view.state.tr.replaceWith(
|
|
148
|
+
getPos(),
|
|
149
|
+
getPos() + node.nodeSize,
|
|
150
|
+
content
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
editor
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return addInlineContentAttributes(
|
|
158
|
+
output,
|
|
159
|
+
inlineContentConfig.type,
|
|
160
|
+
node.attrs as Props<T["propSchema"]>,
|
|
161
|
+
inlineContentConfig.propSchema
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
},
|
|
113
165
|
});
|
|
114
166
|
|
|
115
167
|
return createInlineContentSpecFromTipTapNode(
|
|
@@ -42,10 +42,10 @@ export declare const audioBlockConfig: {
|
|
|
42
42
|
};
|
|
43
43
|
export declare const audioRender: (block: BlockFromConfig<typeof audioBlockConfig, any, any>, editor: BlockNoteEditor<any, any, any>) => {
|
|
44
44
|
dom: HTMLDivElement;
|
|
45
|
-
destroy
|
|
45
|
+
destroy?: undefined;
|
|
46
46
|
} | {
|
|
47
47
|
dom: HTMLDivElement;
|
|
48
|
-
destroy
|
|
48
|
+
destroy: (() => void) | undefined;
|
|
49
49
|
};
|
|
50
50
|
export declare const audioParse: (element: HTMLElement) => Partial<Props<typeof audioBlockConfig.propSchema>> | undefined;
|
|
51
51
|
export declare const audioToExternalHTML: (block: BlockFromConfig<typeof audioBlockConfig, any, any>) => {
|
|
@@ -35,10 +35,10 @@ export declare const fileBlockConfig: {
|
|
|
35
35
|
};
|
|
36
36
|
export declare const fileRender: (block: BlockFromConfig<typeof fileBlockConfig, any, any>, editor: BlockNoteEditor<any, any, any>) => {
|
|
37
37
|
dom: HTMLDivElement;
|
|
38
|
-
destroy
|
|
38
|
+
destroy?: undefined;
|
|
39
39
|
} | {
|
|
40
40
|
dom: HTMLDivElement;
|
|
41
|
-
destroy
|
|
41
|
+
destroy: (() => void) | undefined;
|
|
42
42
|
};
|
|
43
43
|
export declare const fileParse: (element: HTMLElement) => {
|
|
44
44
|
url: string | undefined;
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
2
|
-
import { BlockFromConfig, FileBlockConfig } from "../../schema";
|
|
2
|
+
import { BlockFromConfig, BlockSchemaWithBlock, FileBlockConfig } from "../../schema";
|
|
3
|
+
export declare const createFileBlockWrapper: (block: BlockFromConfig<FileBlockConfig, any, any>, editor: BlockNoteEditor<BlockSchemaWithBlock<FileBlockConfig["type"], FileBlockConfig>, any, any>, element: {
|
|
4
|
+
dom: HTMLElement;
|
|
5
|
+
destroy?: () => void;
|
|
6
|
+
}, buttonText?: string, buttonIcon?: HTMLElement) => {
|
|
7
|
+
dom: HTMLDivElement;
|
|
8
|
+
destroy?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
dom: HTMLDivElement;
|
|
11
|
+
destroy: (() => void) | undefined;
|
|
12
|
+
};
|
|
3
13
|
export declare const createDefaultFilePreview: (block: BlockFromConfig<FileBlockConfig, any, any>) => {
|
|
4
14
|
dom: HTMLElement;
|
|
5
15
|
destroy?: () => void;
|
|
@@ -56,10 +56,10 @@ export declare const imageBlockConfig: {
|
|
|
56
56
|
};
|
|
57
57
|
export declare const imageRender: (block: BlockFromConfig<typeof imageBlockConfig, any, any>, editor: BlockNoteEditor<any, any, any>) => {
|
|
58
58
|
dom: HTMLDivElement;
|
|
59
|
-
destroy
|
|
59
|
+
destroy?: undefined;
|
|
60
60
|
} | {
|
|
61
61
|
dom: HTMLDivElement;
|
|
62
|
-
destroy
|
|
62
|
+
destroy: (() => void) | undefined;
|
|
63
63
|
};
|
|
64
64
|
export declare const imageParse: (element: HTMLElement) => Partial<Props<typeof imageBlockConfig.propSchema>> | undefined;
|
|
65
65
|
export declare const imageToExternalHTML: (block: BlockFromConfig<typeof imageBlockConfig, any, any>) => {
|
|
@@ -56,10 +56,10 @@ export declare const videoBlockConfig: {
|
|
|
56
56
|
};
|
|
57
57
|
export declare const videoRender: (block: BlockFromConfig<typeof videoBlockConfig, any, any>, editor: BlockNoteEditor<any, any, any>) => {
|
|
58
58
|
dom: HTMLDivElement;
|
|
59
|
-
destroy
|
|
59
|
+
destroy?: undefined;
|
|
60
60
|
} | {
|
|
61
61
|
dom: HTMLDivElement;
|
|
62
|
-
destroy
|
|
62
|
+
destroy: (() => void) | undefined;
|
|
63
63
|
};
|
|
64
64
|
export declare const videoParse: (element: HTMLElement) => Partial<Props<typeof videoBlockConfig.propSchema>> | undefined;
|
|
65
65
|
export declare const videoToExternalHTML: (block: BlockFromConfig<typeof videoBlockConfig, any, any>) => {
|
|
@@ -8,7 +8,7 @@ import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkTool
|
|
|
8
8
|
import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin";
|
|
9
9
|
import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin";
|
|
10
10
|
import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin";
|
|
11
|
-
import { BlockIdentifier, BlockNoteDOMAttributes, BlockSchema, BlockSpecs, InlineContentSchema, InlineContentSpecs, PartialInlineContent, StyleSchema, StyleSpecs
|
|
11
|
+
import { BlockIdentifier, BlockNoteDOMAttributes, BlockSchema, BlockSpecs, InlineContentSchema, InlineContentSpecs, PartialInlineContent, Styles, StyleSchema, StyleSpecs } from "../schema";
|
|
12
12
|
import { NoInfer } from "../util/typescript";
|
|
13
13
|
import { TextCursorPosition } from "./cursorPositionTypes";
|
|
14
14
|
import { Selection } from "./selectionTypes";
|
|
@@ -18,6 +18,12 @@ import { Dictionary } from "../i18n/dictionary";
|
|
|
18
18
|
import { Transaction } from "@tiptap/pm/state";
|
|
19
19
|
import "../style.css";
|
|
20
20
|
export type BlockNoteEditorOptions<BSchema extends BlockSchema, ISchema extends InlineContentSchema, SSchema extends StyleSchema> = {
|
|
21
|
+
/**
|
|
22
|
+
* Whether changes to blocks (like indentation, creating lists, changing headings) should be animated or not. Defaults to `true`.
|
|
23
|
+
*
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
animations?: boolean;
|
|
21
27
|
disableExtensions: string[];
|
|
22
28
|
/**
|
|
23
29
|
* A dictionary object containing translations for the editor.
|
|
@@ -53,7 +59,7 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema, ISchema extends
|
|
|
53
59
|
* @param file The file that should be uploaded.
|
|
54
60
|
* @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
|
|
55
61
|
*/
|
|
56
|
-
uploadFile: (file: File) => Promise<string | Record<string, any>>;
|
|
62
|
+
uploadFile: (file: File, blockId?: string) => Promise<string | Record<string, any>>;
|
|
57
63
|
/**
|
|
58
64
|
* Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or
|
|
59
65
|
* implementing custom protocols / schemes
|
|
@@ -94,9 +100,18 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema, ISchema extends
|
|
|
94
100
|
* You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
|
|
95
101
|
*/
|
|
96
102
|
_headless: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* A flag indicating whether to set an HTML ID for every block
|
|
105
|
+
*
|
|
106
|
+
* When set to `true`, on each block an id attribute will be set with the block id
|
|
107
|
+
* Otherwise, the HTML ID attribute will not be set.
|
|
108
|
+
*
|
|
109
|
+
* (note that the id is always set on the `data-id` attribute)
|
|
110
|
+
*/
|
|
111
|
+
setIdAttribute?: boolean;
|
|
97
112
|
};
|
|
98
113
|
export declare class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema> {
|
|
99
|
-
|
|
114
|
+
protected readonly options: Partial<BlockNoteEditorOptions<any, any, any>>;
|
|
100
115
|
private readonly _pmSchema;
|
|
101
116
|
/**
|
|
102
117
|
* Boolean indicating whether the editor is in headless mode.
|
|
@@ -146,7 +161,9 @@ export declare class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockS
|
|
|
146
161
|
* @param file The file that should be uploaded.
|
|
147
162
|
* @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
|
|
148
163
|
*/
|
|
149
|
-
readonly uploadFile: ((file: File) => Promise<string | Record<string, any>>) | undefined;
|
|
164
|
+
readonly uploadFile: ((file: File, blockId?: string) => Promise<string | Record<string, any>>) | undefined;
|
|
165
|
+
private onUploadStartCallbacks;
|
|
166
|
+
private onUploadEndCallbacks;
|
|
150
167
|
readonly resolveFileUrl: (url: string) => Promise<string>;
|
|
151
168
|
get pmSchema(): Schema<any, any>;
|
|
152
169
|
static create<BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema>(options?: Partial<BlockNoteEditorOptions<BSchema, ISchema, SSchema>>): BlockNoteEditor<BSchema, ISchema, SSchema>;
|
|
@@ -162,6 +179,8 @@ export declare class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockS
|
|
|
162
179
|
get domElement(): HTMLDivElement;
|
|
163
180
|
isFocused(): boolean;
|
|
164
181
|
focus(): void;
|
|
182
|
+
onUploadStart(callback: (blockId?: string) => void): () => void;
|
|
183
|
+
onUploadEnd(callback: (blockId?: string) => void): () => void;
|
|
165
184
|
/**
|
|
166
185
|
* @deprecated, use `editor.document` instead
|
|
167
186
|
*/
|
|
@@ -27,5 +27,5 @@ export declare class FilePanelProsemirrorPlugin<I extends InlineContentSchema, S
|
|
|
27
27
|
constructor(editor: BlockNoteEditor<Record<string, FileBlockConfig>, I, S>);
|
|
28
28
|
get shown(): boolean;
|
|
29
29
|
onUpdate(callback: (state: FilePanelState<I, S>) => void): () => void;
|
|
30
|
-
closeMenu: () => void;
|
|
30
|
+
closeMenu: () => void | undefined;
|
|
31
31
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Editor } from "@tiptap/core";
|
|
1
2
|
import { TagParseRule } from "@tiptap/pm/model";
|
|
3
|
+
import { NodeView } from "@tiptap/pm/view";
|
|
2
4
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
3
5
|
import { InlineContentSchema } from "../inlineContent/types";
|
|
4
6
|
import { StyleSchema } from "../styles/types";
|
|
@@ -28,6 +30,7 @@ export type CustomBlockImplementation<T extends CustomBlockConfig, I extends Inl
|
|
|
28
30
|
};
|
|
29
31
|
parse?: (el: HTMLElement) => PartialBlockFromConfig<T, I, S>["props"] | undefined;
|
|
30
32
|
};
|
|
33
|
+
export declare function applyNonSelectableBlockFix(nodeView: NodeView, editor: Editor): void;
|
|
31
34
|
export declare function getParseRules(config: BlockConfig, customParseFunction: CustomBlockImplementation<any, any, any>["parse"]): TagParseRule[];
|
|
32
35
|
export declare function createBlockSpec<T extends CustomBlockConfig, I extends InlineContentSchema, S extends StyleSchema>(blockConfig: T, blockImplementation: CustomBlockImplementation<T, I, S>): {
|
|
33
36
|
config: T;
|
|
@@ -28,6 +28,7 @@ export type FileBlockConfig = {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
content: "none";
|
|
31
|
+
isSelectable?: boolean;
|
|
31
32
|
isFileBlock: true;
|
|
32
33
|
fileBlockAccept?: string[];
|
|
33
34
|
};
|
|
@@ -35,6 +36,7 @@ export type BlockConfig = {
|
|
|
35
36
|
type: string;
|
|
36
37
|
readonly propSchema: PropSchema;
|
|
37
38
|
content: "inline" | "none" | "table";
|
|
39
|
+
isSelectable?: boolean;
|
|
38
40
|
isFileBlock?: false;
|
|
39
41
|
} | FileBlockConfig;
|
|
40
42
|
export type TiptapBlockImplementation<T extends BlockConfig, B extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema> = {
|