@blocknote/core 0.9.4 → 0.9.6
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 +1219 -1177
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +7 -7
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/BlockNoteEditor.ts +19 -7
- package/src/api/nodeConversions/nodeConversions.ts +0 -13
- package/src/extensions/Blocks/api/block.ts +33 -1
- package/src/extensions/Blocks/nodes/Block.module.css +1 -4
- package/src/extensions/Blocks/nodes/BlockContainer.ts +49 -1
- package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.ts +30 -0
- package/src/extensions/SideMenu/SideMenuPlugin.ts +11 -0
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +4 -0
- package/types/src/BlockNoteEditor.d.ts +2 -1
- package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +1 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/Image.d.ts +0 -6
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.d.ts +0 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://github.com/TypeCellOS/BlockNote",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "0.9.
|
|
6
|
+
"version": "0.9.6",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"types",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"access": "public",
|
|
110
110
|
"registry": "https://registry.npmjs.org/"
|
|
111
111
|
},
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "7f4c6fd01941eef3b5ba5c3e670b02768322e482"
|
|
113
113
|
}
|
package/src/BlockNoteEditor.ts
CHANGED
|
@@ -140,7 +140,7 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
|
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
// tiptap options, undocumented
|
|
143
|
-
_tiptapOptions:
|
|
143
|
+
_tiptapOptions: Partial<EditorOptions>;
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
const blockNoteTipTapOptions = {
|
|
@@ -228,10 +228,11 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
228
228
|
},
|
|
229
229
|
]);
|
|
230
230
|
|
|
231
|
-
const tiptapOptions: EditorOptions = {
|
|
231
|
+
const tiptapOptions: Partial<EditorOptions> = {
|
|
232
232
|
...blockNoteTipTapOptions,
|
|
233
233
|
...newOptions._tiptapOptions,
|
|
234
234
|
onBeforeCreate(editor) {
|
|
235
|
+
newOptions._tiptapOptions?.onBeforeCreate?.(editor);
|
|
235
236
|
if (!initialContent) {
|
|
236
237
|
// when using collaboration
|
|
237
238
|
return;
|
|
@@ -250,7 +251,8 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
250
251
|
);
|
|
251
252
|
editor.editor.options.content = root.toJSON();
|
|
252
253
|
},
|
|
253
|
-
onCreate: () => {
|
|
254
|
+
onCreate: (editor) => {
|
|
255
|
+
newOptions._tiptapOptions?.onCreate?.(editor);
|
|
254
256
|
// We need to wait for the TipTap editor to init before we can set the
|
|
255
257
|
// initial content, as the schema may contain custom blocks which need
|
|
256
258
|
// it to render.
|
|
@@ -261,7 +263,8 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
261
263
|
newOptions.onEditorReady?.(this);
|
|
262
264
|
this.ready = true;
|
|
263
265
|
},
|
|
264
|
-
onUpdate: () => {
|
|
266
|
+
onUpdate: (editor) => {
|
|
267
|
+
newOptions._tiptapOptions?.onUpdate?.(editor);
|
|
265
268
|
// This seems to be necessary due to a bug in TipTap:
|
|
266
269
|
// https://github.com/ueberdosis/tiptap/issues/2583
|
|
267
270
|
if (!this.ready) {
|
|
@@ -270,7 +273,8 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
270
273
|
|
|
271
274
|
newOptions.onEditorContentChange?.(this);
|
|
272
275
|
},
|
|
273
|
-
onSelectionUpdate: () => {
|
|
276
|
+
onSelectionUpdate: (editor) => {
|
|
277
|
+
newOptions._tiptapOptions?.onSelectionUpdate?.(editor);
|
|
274
278
|
// This seems to be necessary due to a bug in TipTap:
|
|
275
279
|
// https://github.com/ueberdosis/tiptap/issues/2583
|
|
276
280
|
if (!this.ready) {
|
|
@@ -279,17 +283,25 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
279
283
|
|
|
280
284
|
newOptions.onTextCursorPositionChange?.(this);
|
|
281
285
|
},
|
|
282
|
-
editable:
|
|
286
|
+
editable:
|
|
287
|
+
options.editable !== undefined
|
|
288
|
+
? options.editable
|
|
289
|
+
: newOptions._tiptapOptions?.editable !== undefined
|
|
290
|
+
? newOptions._tiptapOptions?.editable
|
|
291
|
+
: true,
|
|
283
292
|
extensions:
|
|
284
293
|
newOptions.enableBlockNoteExtensions === false
|
|
285
|
-
? newOptions._tiptapOptions?.extensions
|
|
294
|
+
? newOptions._tiptapOptions?.extensions || []
|
|
286
295
|
: [...(newOptions._tiptapOptions?.extensions || []), ...extensions],
|
|
287
296
|
editorProps: {
|
|
297
|
+
...newOptions._tiptapOptions?.editorProps,
|
|
288
298
|
attributes: {
|
|
299
|
+
...newOptions._tiptapOptions?.editorProps?.attributes,
|
|
289
300
|
...newOptions.domAttributes?.editor,
|
|
290
301
|
class: mergeCSSClasses(
|
|
291
302
|
styles.bnEditor,
|
|
292
303
|
styles.bnRoot,
|
|
304
|
+
newOptions.domAttributes?.editor?.class || "",
|
|
293
305
|
newOptions.defaultStyles ? styles.defaultStyles : "",
|
|
294
306
|
newOptions.domAttributes?.editor?.class || ""
|
|
295
307
|
),
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
BlockSchema,
|
|
6
6
|
PartialBlock,
|
|
7
7
|
} from "../../extensions/Blocks/api/blockTypes";
|
|
8
|
-
import { defaultProps } from "../../extensions/Blocks/api/defaultProps";
|
|
9
8
|
import {
|
|
10
9
|
ColorStyle,
|
|
11
10
|
InlineContent,
|
|
@@ -394,18 +393,6 @@ export function nodeToBlock<BSchema extends BlockSchema>(
|
|
|
394
393
|
if (attr in propSchema) {
|
|
395
394
|
props[attr] = value;
|
|
396
395
|
}
|
|
397
|
-
// Block ids are stored as node attributes the same way props are, so we
|
|
398
|
-
// need to ensure we don't attempt to read block ids as props.
|
|
399
|
-
|
|
400
|
-
// the second check is for the backgroundColor & textColor props.
|
|
401
|
-
// Since we want them to be inherited by child blocks, we can't put them on the blockContent node,
|
|
402
|
-
// and instead have to put them on the blockContainer node.
|
|
403
|
-
// The blockContainer node is the same for all block types, but some custom blocks might not use backgroundColor & textColor,
|
|
404
|
-
// so these 2 props are technically unexpected but we shouldn't log a warning.
|
|
405
|
-
// (this is a bit hacky)
|
|
406
|
-
else if (attr !== "id" && !(attr in defaultProps)) {
|
|
407
|
-
console.warn("Block has an unrecognized attribute: " + attr);
|
|
408
|
-
}
|
|
409
396
|
}
|
|
410
397
|
|
|
411
398
|
const blockSpec = blockSchema[blockInfo.contentType.name];
|
|
@@ -38,7 +38,39 @@ export function propsToAttributes<
|
|
|
38
38
|
// Props are displayed in kebab-case as HTML attributes. If a prop's
|
|
39
39
|
// value is the same as its default, we don't display an HTML
|
|
40
40
|
// attribute for it.
|
|
41
|
-
parseHTML: (element) =>
|
|
41
|
+
parseHTML: (element) => {
|
|
42
|
+
const value = element.getAttribute(camelToDataKebab(name));
|
|
43
|
+
|
|
44
|
+
if (value === null) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof spec.default === "boolean") {
|
|
49
|
+
if (value === "true") {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (value === "false") {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof spec.default === "number") {
|
|
61
|
+
const asNumber = parseFloat(value);
|
|
62
|
+
const isNumeric =
|
|
63
|
+
!Number.isNaN(asNumber) && Number.isFinite(asNumber);
|
|
64
|
+
|
|
65
|
+
if (isNumeric) {
|
|
66
|
+
return asNumber;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return value;
|
|
73
|
+
},
|
|
42
74
|
renderHTML: (attributes) =>
|
|
43
75
|
attributes[name] !== spec.default
|
|
44
76
|
? {
|
|
@@ -23,6 +23,7 @@ BASIC STYLES
|
|
|
23
23
|
padding: 3px 0;
|
|
24
24
|
flex-grow: 1;
|
|
25
25
|
transition: font-size 0.2s;
|
|
26
|
+
width: 100%;
|
|
26
27
|
/*
|
|
27
28
|
because the content elements are display: block
|
|
28
29
|
we use flex to position them next to list markers
|
|
@@ -287,10 +288,6 @@ NESTED BLOCKS
|
|
|
287
288
|
cursor: ew-resize;
|
|
288
289
|
}
|
|
289
290
|
|
|
290
|
-
[data-content-type="image"] .imageWrapper:hover .resizeHandle {
|
|
291
|
-
display: block;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
291
|
[data-content-type="image"] .caption {
|
|
295
292
|
font-size: 0.8em
|
|
296
293
|
}
|
|
@@ -485,6 +485,51 @@ export const BlockContainer = Node.create<{
|
|
|
485
485
|
}),
|
|
486
486
|
]);
|
|
487
487
|
|
|
488
|
+
const handleDelete = () =>
|
|
489
|
+
this.editor.commands.first(({ commands }) => [
|
|
490
|
+
// Deletes the selection if it's not empty.
|
|
491
|
+
() => commands.deleteSelection(),
|
|
492
|
+
// Merges block with the next one (at the same nesting level or lower),
|
|
493
|
+
// if one exists, the block has no children, and the selection is at the
|
|
494
|
+
// end of the block.
|
|
495
|
+
() =>
|
|
496
|
+
commands.command(({ state }) => {
|
|
497
|
+
const { node, contentNode, depth, endPos } = getBlockInfoFromPos(
|
|
498
|
+
state.doc,
|
|
499
|
+
state.selection.from
|
|
500
|
+
)!;
|
|
501
|
+
|
|
502
|
+
const blockAtDocEnd = false;
|
|
503
|
+
const selectionAtBlockEnd =
|
|
504
|
+
state.selection.$anchor.parentOffset ===
|
|
505
|
+
contentNode.firstChild!.nodeSize;
|
|
506
|
+
const selectionEmpty =
|
|
507
|
+
state.selection.anchor === state.selection.head;
|
|
508
|
+
const hasChildBlocks = node.childCount === 2;
|
|
509
|
+
|
|
510
|
+
if (
|
|
511
|
+
!blockAtDocEnd &&
|
|
512
|
+
selectionAtBlockEnd &&
|
|
513
|
+
selectionEmpty &&
|
|
514
|
+
!hasChildBlocks
|
|
515
|
+
) {
|
|
516
|
+
let oldDepth = depth;
|
|
517
|
+
let newPos = endPos + 2;
|
|
518
|
+
let newDepth = state.doc.resolve(newPos).depth;
|
|
519
|
+
|
|
520
|
+
while (newDepth < oldDepth) {
|
|
521
|
+
oldDepth = newDepth;
|
|
522
|
+
newPos += 2;
|
|
523
|
+
newDepth = state.doc.resolve(newPos).depth;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return commands.BNMergeBlocks(newPos - 1);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return false;
|
|
530
|
+
}),
|
|
531
|
+
]);
|
|
532
|
+
|
|
488
533
|
const handleEnter = () =>
|
|
489
534
|
this.editor.commands.first(({ commands }) => [
|
|
490
535
|
// Removes a level of nesting if the block is empty & indented, while the selection is also empty & at the start
|
|
@@ -552,12 +597,14 @@ export const BlockContainer = Node.create<{
|
|
|
552
597
|
state.selection.from
|
|
553
598
|
)!;
|
|
554
599
|
|
|
600
|
+
const selectionAtBlockStart =
|
|
601
|
+
state.selection.$anchor.parentOffset === 0;
|
|
555
602
|
const blockEmpty = node.textContent.length === 0;
|
|
556
603
|
|
|
557
604
|
if (!blockEmpty) {
|
|
558
605
|
chain()
|
|
559
606
|
.deleteSelection()
|
|
560
|
-
.BNSplitBlock(state.selection.from,
|
|
607
|
+
.BNSplitBlock(state.selection.from, selectionAtBlockStart)
|
|
561
608
|
.run();
|
|
562
609
|
|
|
563
610
|
return true;
|
|
@@ -569,6 +616,7 @@ export const BlockContainer = Node.create<{
|
|
|
569
616
|
|
|
570
617
|
return {
|
|
571
618
|
Backspace: handleBackspace,
|
|
619
|
+
Delete: handleDelete,
|
|
572
620
|
Enter: handleEnter,
|
|
573
621
|
// Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
|
|
574
622
|
// editor since the browser will try to use tab for keyboard navigation.
|
|
@@ -225,6 +225,34 @@ const renderImage = (
|
|
|
225
225
|
);
|
|
226
226
|
};
|
|
227
227
|
|
|
228
|
+
// Shows the resize handles when hovering over the image with the cursor.
|
|
229
|
+
const imageMouseEnterHandler = () => {
|
|
230
|
+
if (editor.isEditable) {
|
|
231
|
+
leftResizeHandle.style.display = "block";
|
|
232
|
+
rightResizeHandle.style.display = "block";
|
|
233
|
+
} else {
|
|
234
|
+
leftResizeHandle.style.display = "none";
|
|
235
|
+
rightResizeHandle.style.display = "none";
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
// Hides the resize handles when the cursor leaves the image, unless the
|
|
239
|
+
// cursor moves to one of the resize handles.
|
|
240
|
+
const imageMouseLeaveHandler = (event: MouseEvent) => {
|
|
241
|
+
if (
|
|
242
|
+
event.relatedTarget === leftResizeHandle ||
|
|
243
|
+
event.relatedTarget === rightResizeHandle
|
|
244
|
+
) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (resizeParams) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
leftResizeHandle.style.display = "none";
|
|
253
|
+
rightResizeHandle.style.display = "none";
|
|
254
|
+
};
|
|
255
|
+
|
|
228
256
|
// Sets the resize params, allowing the user to begin resizing the image by
|
|
229
257
|
// moving the cursor left or right.
|
|
230
258
|
const leftResizeHandleMouseDownHandler = (event: MouseEvent) => {
|
|
@@ -266,6 +294,8 @@ const renderImage = (
|
|
|
266
294
|
window.addEventListener("mouseup", windowMouseUpHandler);
|
|
267
295
|
addImageButton.addEventListener("mousedown", addImageButtonMouseDownHandler);
|
|
268
296
|
addImageButton.addEventListener("click", addImageButtonClickHandler);
|
|
297
|
+
image.addEventListener("mouseenter", imageMouseEnterHandler);
|
|
298
|
+
image.addEventListener("mouseleave", imageMouseLeaveHandler);
|
|
269
299
|
leftResizeHandle.addEventListener(
|
|
270
300
|
"mousedown",
|
|
271
301
|
leftResizeHandleMouseDownHandler
|
|
@@ -265,6 +265,8 @@ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
|
|
|
265
265
|
// Makes menu scroll with the page.
|
|
266
266
|
document.addEventListener("scroll", this.onScroll);
|
|
267
267
|
|
|
268
|
+
// Unfreezes the menu whenever the user clicks anywhere.
|
|
269
|
+
document.body.addEventListener("mousedown", this.onMouseDown, true);
|
|
268
270
|
// Hides and unfreezes the menu whenever the user presses a key.
|
|
269
271
|
document.body.addEventListener("keydown", this.onKeyDown, true);
|
|
270
272
|
}
|
|
@@ -347,6 +349,14 @@ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
|
|
|
347
349
|
this.menuFrozen = false;
|
|
348
350
|
};
|
|
349
351
|
|
|
352
|
+
onMouseDown = (_event: MouseEvent) => {
|
|
353
|
+
if (this.sideMenuState && !this.sideMenuState.show) {
|
|
354
|
+
this.sideMenuState.show = true;
|
|
355
|
+
this.updateSideMenu(this.sideMenuState);
|
|
356
|
+
}
|
|
357
|
+
this.menuFrozen = false;
|
|
358
|
+
};
|
|
359
|
+
|
|
350
360
|
onMouseMove = (event: MouseEvent) => {
|
|
351
361
|
if (this.menuFrozen) {
|
|
352
362
|
return;
|
|
@@ -479,6 +489,7 @@ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
|
|
|
479
489
|
this.pmView.dom.removeEventListener("dragstart", this.onDragStart);
|
|
480
490
|
document.body.removeEventListener("drop", this.onDrop, true);
|
|
481
491
|
document.removeEventListener("scroll", this.onScroll);
|
|
492
|
+
document.body.removeEventListener("mousedown", this.onMouseDown, true);
|
|
482
493
|
document.body.removeEventListener("keydown", this.onKeyDown, true);
|
|
483
494
|
}
|
|
484
495
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EditorOptions } from "@tiptap/core";
|
|
1
2
|
import { Node } from "prosemirror-model";
|
|
2
3
|
import { Editor as TiptapEditor } from "@tiptap/core/dist/packages/core/src/Editor";
|
|
3
4
|
import * as Y from "yjs";
|
|
@@ -93,7 +94,7 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
|
|
|
93
94
|
*/
|
|
94
95
|
renderCursor?: (user: any) => HTMLElement;
|
|
95
96
|
};
|
|
96
|
-
_tiptapOptions:
|
|
97
|
+
_tiptapOptions: Partial<EditorOptions>;
|
|
97
98
|
};
|
|
98
99
|
export declare class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
99
100
|
private readonly options;
|
|
@@ -36,6 +36,7 @@ export declare class SideMenuView<BSchema extends BlockSchema> implements Plugin
|
|
|
36
36
|
*/
|
|
37
37
|
onDragOver: (event: DragEvent) => void;
|
|
38
38
|
onKeyDown: (_event: KeyboardEvent) => void;
|
|
39
|
+
onMouseDown: (_event: MouseEvent) => void;
|
|
39
40
|
onMouseMove: (event: MouseEvent) => void;
|
|
40
41
|
onScroll: () => void;
|
|
41
42
|
destroy(): void;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import "@uppy/core/dist/style.css";
|
|
2
|
-
import "@uppy/dashboard/dist/style.css";
|
|
3
|
-
import "@uppy/drag-drop/dist/style.css";
|
|
4
|
-
import "@uppy/file-input/dist/style.css";
|
|
5
|
-
import "@uppy/progress-bar/dist/style.css";
|
|
6
|
-
export declare const Image: BlockSpec<BType, PSchema, ContainsInlineContent>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const uploadToTmpFilesOrg: (file: File) => Promise<any>;
|