@blocknote/core 0.15.6 → 0.15.9
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 +1486 -1350
- 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 +81 -17
- package/src/blocks/AudioBlockContent/AudioBlockContent.ts +24 -48
- package/src/blocks/FileBlockContent/FileBlockContent.ts +4 -22
- package/src/blocks/FileBlockContent/fileBlockHelpers.ts +72 -1
- package/src/blocks/ImageBlockContent/ImageBlockContent.ts +36 -62
- package/src/blocks/VideoBlockContent/VideoBlockContent.ts +34 -59
- package/src/editor/BlockNoteEditor.test.ts +13 -0
- package/src/editor/BlockNoteEditor.ts +89 -13
- 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 +23 -12
- package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +22 -10
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +6 -1
- 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/types.ts +1 -1
- package/src/schema/inlineContent/createSpec.ts +54 -5
- package/types/src/api/testUtil/cases/customBlocks.d.ts +6 -6
- package/types/src/api/testUtil/cases/customInlineContent.d.ts +6 -6
- package/types/src/api/testUtil/cases/customStyles.d.ts +6 -6
- package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +5 -5
- 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 +5 -5
- package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +5 -5
- package/types/src/blocks/defaultBlocks.d.ts +12 -12
- package/types/src/editor/BlockNoteEditor.d.ts +27 -5
- package/types/src/editor/BlockNoteExtensions.d.ts +1 -0
- package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +1 -1
- package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +1 -0
- package/types/src/schema/blocks/types.d.ts +1 -1
- package/types/src/schema/inlineContent/createSpec.d.ts +3 -3
|
@@ -41,9 +41,9 @@ import {
|
|
|
41
41
|
InlineContentSchema,
|
|
42
42
|
InlineContentSpecs,
|
|
43
43
|
PartialInlineContent,
|
|
44
|
+
Styles,
|
|
44
45
|
StyleSchema,
|
|
45
46
|
StyleSpecs,
|
|
46
|
-
Styles,
|
|
47
47
|
} from "../schema";
|
|
48
48
|
import { mergeCSSClasses } from "../util/browser";
|
|
49
49
|
import { NoInfer, UnreachableCaseError } from "../util/typescript";
|
|
@@ -67,6 +67,7 @@ import { en } from "../i18n/locales";
|
|
|
67
67
|
|
|
68
68
|
import { Transaction } from "@tiptap/pm/state";
|
|
69
69
|
import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer";
|
|
70
|
+
import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin";
|
|
70
71
|
import "../style.css";
|
|
71
72
|
import { initializeESMDependencies } from "../util/esmDependencies";
|
|
72
73
|
|
|
@@ -75,6 +76,13 @@ export type BlockNoteEditorOptions<
|
|
|
75
76
|
ISchema extends InlineContentSchema,
|
|
76
77
|
SSchema extends StyleSchema
|
|
77
78
|
> = {
|
|
79
|
+
/**
|
|
80
|
+
* Whether changes to blocks (like indentation, creating lists, changing headings) should be animated or not. Defaults to `true`.
|
|
81
|
+
*
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
|
+
animations?: boolean;
|
|
85
|
+
|
|
78
86
|
disableExtensions: string[];
|
|
79
87
|
/**
|
|
80
88
|
* A dictionary object containing translations for the editor.
|
|
@@ -119,7 +127,10 @@ export type BlockNoteEditorOptions<
|
|
|
119
127
|
* @param file The file that should be uploaded.
|
|
120
128
|
* @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
|
|
121
129
|
*/
|
|
122
|
-
uploadFile: (
|
|
130
|
+
uploadFile: (
|
|
131
|
+
file: File,
|
|
132
|
+
blockId?: string
|
|
133
|
+
) => Promise<string | Record<string, any>>;
|
|
123
134
|
|
|
124
135
|
/**
|
|
125
136
|
* Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or
|
|
@@ -166,6 +177,16 @@ export type BlockNoteEditorOptions<
|
|
|
166
177
|
* You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
|
|
167
178
|
*/
|
|
168
179
|
_headless: boolean;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A flag indicating whether to set an HTML ID for every block
|
|
183
|
+
*
|
|
184
|
+
* When set to `true`, on each block an id attribute will be set with the block id
|
|
185
|
+
* Otherwise, the HTML ID attribute will not be set.
|
|
186
|
+
*
|
|
187
|
+
* (note that the id is always set on the `data-id` attribute)
|
|
188
|
+
*/
|
|
189
|
+
setIdAttribute?: boolean;
|
|
169
190
|
};
|
|
170
191
|
|
|
171
192
|
const blockNoteTipTapOptions = {
|
|
@@ -255,9 +276,12 @@ export class BlockNoteEditor<
|
|
|
255
276
|
* @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
|
|
256
277
|
*/
|
|
257
278
|
public readonly uploadFile:
|
|
258
|
-
| ((file: File) => Promise<string | Record<string, any>>)
|
|
279
|
+
| ((file: File, blockId?: string) => Promise<string | Record<string, any>>)
|
|
259
280
|
| undefined;
|
|
260
281
|
|
|
282
|
+
private onUploadStartCallbacks: ((blockId?: string) => void)[] = [];
|
|
283
|
+
private onUploadEndCallbacks: ((blockId?: string) => void)[] = [];
|
|
284
|
+
|
|
261
285
|
public readonly resolveFileUrl: (url: string) => Promise<string>;
|
|
262
286
|
|
|
263
287
|
public get pmSchema() {
|
|
@@ -273,7 +297,7 @@ export class BlockNoteEditor<
|
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
protected constructor(
|
|
276
|
-
|
|
300
|
+
protected readonly options: Partial<BlockNoteEditorOptions<any, any, any>>
|
|
277
301
|
) {
|
|
278
302
|
const anyOpts = options as any;
|
|
279
303
|
if (anyOpts.onEditorContentChange) {
|
|
@@ -339,6 +363,7 @@ export class BlockNoteEditor<
|
|
|
339
363
|
collaboration: newOptions.collaboration,
|
|
340
364
|
trailingBlock: newOptions.trailingBlock,
|
|
341
365
|
disableExtensions: newOptions.disableExtensions,
|
|
366
|
+
setIdAttribute: newOptions.setIdAttribute,
|
|
342
367
|
});
|
|
343
368
|
|
|
344
369
|
const blockNoteUIExtension = Extension.create({
|
|
@@ -353,12 +378,30 @@ export class BlockNoteEditor<
|
|
|
353
378
|
...(this.filePanel ? [this.filePanel.plugin] : []),
|
|
354
379
|
...(this.tableHandles ? [this.tableHandles.plugin] : []),
|
|
355
380
|
PlaceholderPlugin(this, newOptions.placeholders),
|
|
381
|
+
...(this.options.animations ?? true
|
|
382
|
+
? [PreviousBlockTypePlugin()]
|
|
383
|
+
: []),
|
|
356
384
|
];
|
|
357
385
|
},
|
|
358
386
|
});
|
|
359
387
|
extensions.push(blockNoteUIExtension);
|
|
360
388
|
|
|
361
|
-
|
|
389
|
+
if (newOptions.uploadFile) {
|
|
390
|
+
const uploadFile = newOptions.uploadFile;
|
|
391
|
+
this.uploadFile = async (file, block) => {
|
|
392
|
+
this.onUploadStartCallbacks.forEach((callback) =>
|
|
393
|
+
callback.apply(this, [block])
|
|
394
|
+
);
|
|
395
|
+
try {
|
|
396
|
+
return await uploadFile(file, block);
|
|
397
|
+
} finally {
|
|
398
|
+
this.onUploadEndCallbacks.forEach((callback) =>
|
|
399
|
+
callback.apply(this, [block])
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
362
405
|
this.resolveFileUrl = newOptions.resolveFileUrl || (async (url) => url);
|
|
363
406
|
this.headless = newOptions._headless;
|
|
364
407
|
|
|
@@ -463,6 +506,28 @@ export class BlockNoteEditor<
|
|
|
463
506
|
this._tiptapEditor.view.focus();
|
|
464
507
|
}
|
|
465
508
|
|
|
509
|
+
public onUploadStart(callback: (blockId?: string) => void) {
|
|
510
|
+
this.onUploadStartCallbacks.push(callback);
|
|
511
|
+
|
|
512
|
+
return () => {
|
|
513
|
+
const index = this.onUploadStartCallbacks.indexOf(callback);
|
|
514
|
+
if (index > -1) {
|
|
515
|
+
this.onUploadStartCallbacks.splice(index, 1);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
public onUploadEnd(callback: (blockId?: string) => void) {
|
|
521
|
+
this.onUploadEndCallbacks.push(callback);
|
|
522
|
+
|
|
523
|
+
return () => {
|
|
524
|
+
const index = this.onUploadEndCallbacks.indexOf(callback);
|
|
525
|
+
if (index > -1) {
|
|
526
|
+
this.onUploadEndCallbacks.splice(index, 1);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
466
531
|
/**
|
|
467
532
|
* @deprecated, use `editor.document` instead
|
|
468
533
|
*/
|
|
@@ -550,7 +615,7 @@ export class BlockNoteEditor<
|
|
|
550
615
|
blockArray: Block<BSchema, ISchema, SSchema>[]
|
|
551
616
|
): boolean {
|
|
552
617
|
for (const block of blockArray) {
|
|
553
|
-
if (
|
|
618
|
+
if (callback(block) === false) {
|
|
554
619
|
return false;
|
|
555
620
|
}
|
|
556
621
|
|
|
@@ -1159,15 +1224,26 @@ export class BlockNoteEditor<
|
|
|
1159
1224
|
};
|
|
1160
1225
|
}
|
|
1161
1226
|
|
|
1162
|
-
public
|
|
1227
|
+
public openSuggestionMenu(
|
|
1228
|
+
triggerCharacter: string,
|
|
1229
|
+
pluginState?: {
|
|
1230
|
+
deleteTriggerCharacter?: boolean;
|
|
1231
|
+
ignoreQueryLength?: boolean;
|
|
1232
|
+
}
|
|
1233
|
+
) {
|
|
1234
|
+
const tr = this.prosemirrorView.state.tr;
|
|
1235
|
+
const transaction =
|
|
1236
|
+
pluginState && pluginState.deleteTriggerCharacter
|
|
1237
|
+
? tr.insertText(triggerCharacter)
|
|
1238
|
+
: tr;
|
|
1239
|
+
|
|
1163
1240
|
this.prosemirrorView.focus();
|
|
1164
1241
|
this.prosemirrorView.dispatch(
|
|
1165
|
-
this.
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
})
|
|
1242
|
+
transaction.scrollIntoView().setMeta(this.suggestionMenus.plugin, {
|
|
1243
|
+
triggerCharacter: triggerCharacter,
|
|
1244
|
+
deleteTriggerCharacter: pluginState?.deleteTriggerCharacter || false,
|
|
1245
|
+
ignoreQueryLength: pluginState?.ignoreQueryLength || false,
|
|
1246
|
+
})
|
|
1171
1247
|
);
|
|
1172
1248
|
}
|
|
1173
1249
|
}
|
|
@@ -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({
|
|
@@ -664,7 +675,7 @@ export class SideMenuView<
|
|
|
664
675
|
}
|
|
665
676
|
|
|
666
677
|
// Focuses and activates the slash menu.
|
|
667
|
-
this.editor.
|
|
678
|
+
this.editor.openSuggestionMenu("/");
|
|
668
679
|
}
|
|
669
680
|
}
|
|
670
681
|
|
|
@@ -11,6 +11,7 @@ const findBlock = findParentNode((node) => node.type.name === "blockContainer");
|
|
|
11
11
|
|
|
12
12
|
export type SuggestionMenuState = UiElementPosition & {
|
|
13
13
|
query: string;
|
|
14
|
+
ignoreQueryLength?: boolean;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
class SuggestionMenuView<
|
|
@@ -34,7 +35,10 @@ class SuggestionMenuView<
|
|
|
34
35
|
throw new Error("Attempting to update uninitialized suggestions menu");
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
emitUpdate(menuName,
|
|
38
|
+
emitUpdate(menuName, {
|
|
39
|
+
...this.state,
|
|
40
|
+
ignoreQueryLength: this.pluginState?.ignoreQueryLength,
|
|
41
|
+
});
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
this.rootEl = this.editor._tiptapEditor.view.root;
|
|
@@ -50,7 +54,10 @@ class SuggestionMenuView<
|
|
|
50
54
|
const decorationNode = this.rootEl?.querySelector(
|
|
51
55
|
`[data-decoration-id="${this.pluginState!.decorationId}"]`
|
|
52
56
|
);
|
|
53
|
-
|
|
57
|
+
if (!decorationNode) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.state.referencePos = decorationNode.getBoundingClientRect();
|
|
54
61
|
this.emitUpdate(this.pluginState!.triggerCharacter!);
|
|
55
62
|
}
|
|
56
63
|
};
|
|
@@ -85,10 +92,10 @@ class SuggestionMenuView<
|
|
|
85
92
|
`[data-decoration-id="${this.pluginState!.decorationId}"]`
|
|
86
93
|
);
|
|
87
94
|
|
|
88
|
-
if (this.editor.isEditable) {
|
|
95
|
+
if (this.editor.isEditable && decorationNode) {
|
|
89
96
|
this.state = {
|
|
90
97
|
show: true,
|
|
91
|
-
referencePos: decorationNode
|
|
98
|
+
referencePos: decorationNode.getBoundingClientRect(),
|
|
92
99
|
query: this.pluginState!.query,
|
|
93
100
|
};
|
|
94
101
|
|
|
@@ -120,7 +127,7 @@ class SuggestionMenuView<
|
|
|
120
127
|
.deleteRange({
|
|
121
128
|
from:
|
|
122
129
|
this.pluginState.queryStartPos! -
|
|
123
|
-
(this.pluginState.
|
|
130
|
+
(this.pluginState.deleteTriggerCharacter
|
|
124
131
|
? this.pluginState.triggerCharacter!.length
|
|
125
132
|
: 0),
|
|
126
133
|
to: this.editor._tiptapEditor.state.selection.from,
|
|
@@ -132,10 +139,11 @@ class SuggestionMenuView<
|
|
|
132
139
|
type SuggestionPluginState =
|
|
133
140
|
| {
|
|
134
141
|
triggerCharacter: string;
|
|
135
|
-
|
|
142
|
+
deleteTriggerCharacter: boolean;
|
|
136
143
|
queryStartPos: number;
|
|
137
144
|
query: string;
|
|
138
145
|
decorationId: string;
|
|
146
|
+
ignoreQueryLength?: boolean;
|
|
139
147
|
}
|
|
140
148
|
| undefined;
|
|
141
149
|
|
|
@@ -194,7 +202,8 @@ export class SuggestionMenuProseMirrorPlugin<
|
|
|
194
202
|
// or null if it should be hidden.
|
|
195
203
|
const suggestionPluginTransactionMeta: {
|
|
196
204
|
triggerCharacter: string;
|
|
197
|
-
|
|
205
|
+
deleteTriggerCharacter?: boolean;
|
|
206
|
+
ignoreQueryLength?: boolean;
|
|
198
207
|
} | null = transaction.getMeta(suggestionMenuPluginKey);
|
|
199
208
|
|
|
200
209
|
// Only opens a menu of no menu is already open
|
|
@@ -206,11 +215,14 @@ export class SuggestionMenuProseMirrorPlugin<
|
|
|
206
215
|
return {
|
|
207
216
|
triggerCharacter:
|
|
208
217
|
suggestionPluginTransactionMeta.triggerCharacter,
|
|
209
|
-
|
|
210
|
-
suggestionPluginTransactionMeta.
|
|
218
|
+
deleteTriggerCharacter:
|
|
219
|
+
suggestionPluginTransactionMeta.deleteTriggerCharacter !==
|
|
220
|
+
false,
|
|
211
221
|
queryStartPos: newState.selection.from,
|
|
212
222
|
query: "",
|
|
213
223
|
decorationId: `id_${Math.floor(Math.random() * 0xffffffff)}`,
|
|
224
|
+
ignoreQueryLength:
|
|
225
|
+
suggestionPluginTransactionMeta?.ignoreQueryLength,
|
|
214
226
|
};
|
|
215
227
|
}
|
|
216
228
|
|
|
@@ -285,7 +297,7 @@ export class SuggestionMenuProseMirrorPlugin<
|
|
|
285
297
|
|
|
286
298
|
// If the menu was opened programmatically by another extension, it may not use a trigger character. In this
|
|
287
299
|
// case, the decoration is set on the whole block instead, as the decoration range would otherwise be empty.
|
|
288
|
-
if (!suggestionPluginState.
|
|
300
|
+
if (!suggestionPluginState.deleteTriggerCharacter) {
|
|
289
301
|
const blockNode = findBlock(state.selection);
|
|
290
302
|
if (blockNode) {
|
|
291
303
|
return DecorationSet.create(state.doc, [
|
|
@@ -272,7 +272,12 @@ export function getDefaultSlashMenuItems<
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
items.push({
|
|
275
|
-
onItemClick: () =>
|
|
275
|
+
onItemClick: () => {
|
|
276
|
+
editor.openSuggestionMenu(":", {
|
|
277
|
+
deleteTriggerCharacter: true,
|
|
278
|
+
ignoreQueryLength: true,
|
|
279
|
+
});
|
|
280
|
+
},
|
|
276
281
|
key: "emoji",
|
|
277
282
|
...editor.dictionary.slash_menu.emoji,
|
|
278
283
|
});
|
|
@@ -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,6 +1,9 @@
|
|
|
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";
|
|
4
7
|
import { propsToAttributes } from "../blocks/internal";
|
|
5
8
|
import { Props } from "../propTypes";
|
|
6
9
|
import { StyleSchema } from "../styles/types";
|
|
@@ -11,15 +14,15 @@ import {
|
|
|
11
14
|
} from "./internal";
|
|
12
15
|
import {
|
|
13
16
|
CustomInlineContentConfig,
|
|
14
|
-
InlineContentConfig,
|
|
15
17
|
InlineContentFromConfig,
|
|
16
18
|
InlineContentSpec,
|
|
19
|
+
PartialCustomInlineContentFromConfig,
|
|
17
20
|
} from "./types";
|
|
18
21
|
|
|
19
22
|
// TODO: support serialization
|
|
20
23
|
|
|
21
24
|
export type CustomInlineContentImplementation<
|
|
22
|
-
T extends
|
|
25
|
+
T extends CustomInlineContentConfig,
|
|
23
26
|
// B extends BlockSchema,
|
|
24
27
|
// I extends InlineContentSchema,
|
|
25
28
|
S extends StyleSchema
|
|
@@ -28,7 +31,10 @@ export type CustomInlineContentImplementation<
|
|
|
28
31
|
/**
|
|
29
32
|
* The custom inline content to render
|
|
30
33
|
*/
|
|
31
|
-
inlineContent: InlineContentFromConfig<T, S
|
|
34
|
+
inlineContent: InlineContentFromConfig<T, S>,
|
|
35
|
+
updateInlineContent: (
|
|
36
|
+
update: PartialCustomInlineContentFromConfig<T, S>
|
|
37
|
+
) => void
|
|
32
38
|
/**
|
|
33
39
|
* The BlockNote editor instance
|
|
34
40
|
* This is typed generically. If you want an editor with your custom schema, you need to
|
|
@@ -100,7 +106,10 @@ export function createInlineContentSpec<
|
|
|
100
106
|
node,
|
|
101
107
|
editor.schema.inlineContentSchema,
|
|
102
108
|
editor.schema.styleSchema
|
|
103
|
-
) as any as InlineContentFromConfig<T, S
|
|
109
|
+
) as any as InlineContentFromConfig<T, S>, // TODO: fix cast
|
|
110
|
+
() => {
|
|
111
|
+
// No-op
|
|
112
|
+
}
|
|
104
113
|
);
|
|
105
114
|
|
|
106
115
|
return addInlineContentAttributes(
|
|
@@ -110,6 +119,46 @@ export function createInlineContentSpec<
|
|
|
110
119
|
inlineContentConfig.propSchema
|
|
111
120
|
);
|
|
112
121
|
},
|
|
122
|
+
|
|
123
|
+
addNodeView() {
|
|
124
|
+
return ({ node, getPos }) => {
|
|
125
|
+
const editor = this.options.editor;
|
|
126
|
+
|
|
127
|
+
const output = inlineContentImplementation.render(
|
|
128
|
+
nodeToCustomInlineContent(
|
|
129
|
+
node,
|
|
130
|
+
editor.schema.inlineContentSchema,
|
|
131
|
+
editor.schema.styleSchema
|
|
132
|
+
) as any as InlineContentFromConfig<T, S>, // TODO: fix cast
|
|
133
|
+
(update) => {
|
|
134
|
+
if (typeof getPos === "boolean") {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const content = inlineContentToNodes(
|
|
139
|
+
[update],
|
|
140
|
+
editor._tiptapEditor.schema,
|
|
141
|
+
editor.schema.styleSchema
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
editor._tiptapEditor.view.dispatch(
|
|
145
|
+
editor._tiptapEditor.view.state.tr.replaceWith(
|
|
146
|
+
getPos(),
|
|
147
|
+
getPos() + node.nodeSize,
|
|
148
|
+
content
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return addInlineContentAttributes(
|
|
155
|
+
output,
|
|
156
|
+
inlineContentConfig.type,
|
|
157
|
+
node.attrs as Props<T["propSchema"]>,
|
|
158
|
+
inlineContentConfig.propSchema
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
},
|
|
113
162
|
});
|
|
114
163
|
|
|
115
164
|
return createInlineContentSpecFromTipTapNode(
|