@blocknote/core 0.14.5 → 0.15.2
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 +1311 -1074
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +6 -6
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +28 -25
- package/src/api/blockManipulation/blockManipulation.ts +21 -21
- package/src/api/exporters/copyExtension.ts +14 -10
- package/src/api/exporters/html/externalHTMLExporter.ts +26 -8
- package/src/api/exporters/html/htmlConversion.test.ts +27 -25
- package/src/api/exporters/html/internalHTMLSerializer.ts +22 -7
- package/src/api/exporters/html/util/sharedHTMLConversion.ts +3 -2
- package/src/api/exporters/markdown/markdownExporter.ts +5 -4
- package/src/api/nodeConversions/nodeConversions.test.ts +9 -6
- package/src/api/parsers/html/parseHTML.test.ts +3 -4
- package/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap +7 -7
- package/src/blocks/FileBlockContent/fileBlockHelpers.ts +1 -1
- package/src/blocks/defaultBlockHelpers.ts +3 -6
- package/src/blocks/defaultBlockTypeGuards.ts +26 -1
- package/src/editor/BlockNoteEditor.test.ts +48 -1
- package/src/editor/BlockNoteEditor.ts +153 -36
- package/src/editor/BlockNoteExtensions.ts +0 -1
- package/src/editor/BlockNoteTipTapEditor.ts +14 -5
- package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +10 -4
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +37 -0
- package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +2 -2
- package/src/extensions/SideMenu/SideMenuPlugin.ts +20 -14
- package/src/extensions/SuggestionMenu/DefaultGridSuggestionItem.ts +4 -0
- package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +2 -2
- package/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts +45 -0
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +13 -6
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +5 -5
- package/src/extensions/TextAlignment/TextAlignmentExtension.ts +7 -3
- package/src/extensions/TextColor/TextColorExtension.ts +7 -3
- package/src/i18n/locales/ar.ts +6 -0
- package/src/i18n/locales/en.ts +19 -13
- package/src/i18n/locales/fr.ts +6 -0
- package/src/i18n/locales/is.ts +6 -0
- package/src/i18n/locales/ja.ts +6 -0
- package/src/i18n/locales/ko.ts +15 -0
- package/src/i18n/locales/nl.ts +11 -0
- package/src/i18n/locales/pl.ts +6 -0
- package/src/i18n/locales/pt.ts +6 -0
- package/src/i18n/locales/ru.ts +322 -309
- package/src/i18n/locales/vi.ts +13 -0
- package/src/i18n/locales/zh.ts +14 -0
- package/src/index.ts +14 -5
- package/src/style.css +2 -7
- package/types/src/api/blockManipulation/blockManipulation.d.ts +1 -1
- package/types/src/api/exporters/copyExtension.d.ts +2 -2
- package/types/src/api/exporters/html/externalHTMLExporter.d.ts +7 -3
- package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +7 -3
- package/types/src/api/exporters/html/util/sharedHTMLConversion.d.ts +5 -3
- package/types/src/api/exporters/markdown/markdownExporter.d.ts +4 -2
- package/types/src/api/parsers/fileDropExtension.d.ts +2 -2
- package/types/src/api/parsers/pasteExtension.d.ts +2 -2
- package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
- package/types/src/blocks/defaultBlockTypeGuards.d.ts +4 -1
- package/types/src/editor/BlockNoteEditor.d.ts +71 -11
- package/types/src/editor/BlockNoteExtensions.d.ts +2 -3
- package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +1 -0
- package/types/src/extensions/SuggestionMenu/DefaultGridSuggestionItem.d.ts +4 -0
- package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +1 -2
- package/types/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.d.ts +4 -0
- package/types/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.d.ts +1 -1
- package/types/src/i18n/locales/en.d.ts +6 -0
- package/types/src/index.d.ts +11 -5
- package/types/src/pm-nodes/BlockContainer.d.ts +2 -8
- package/types/src/pm-nodes/BlockGroup.d.ts +2 -7
- package/types/src/schema/inlineContent/internal.d.ts +1 -1
|
@@ -4,7 +4,7 @@ import rehypeRemark from "rehype-remark";
|
|
|
4
4
|
import remarkGfm from "remark-gfm";
|
|
5
5
|
import remarkStringify from "remark-stringify";
|
|
6
6
|
import { unified } from "unified";
|
|
7
|
-
import {
|
|
7
|
+
import { PartialBlock } from "../../../blocks/defaultBlocks";
|
|
8
8
|
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
|
|
9
9
|
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
|
|
10
10
|
import { createExternalHTMLExporter } from "../html/externalHTMLExporter";
|
|
@@ -29,12 +29,13 @@ export function blocksToMarkdown<
|
|
|
29
29
|
I extends InlineContentSchema,
|
|
30
30
|
S extends StyleSchema
|
|
31
31
|
>(
|
|
32
|
-
blocks:
|
|
32
|
+
blocks: PartialBlock<BSchema, I, S>[],
|
|
33
33
|
schema: Schema,
|
|
34
|
-
editor: BlockNoteEditor<BSchema, I, S
|
|
34
|
+
editor: BlockNoteEditor<BSchema, I, S>,
|
|
35
|
+
options: { document?: Document }
|
|
35
36
|
): string {
|
|
36
37
|
const exporter = createExternalHTMLExporter(schema, editor);
|
|
37
|
-
const externalHTML = exporter.exportBlocks(blocks);
|
|
38
|
+
const externalHTML = exporter.exportBlocks(blocks, options);
|
|
38
39
|
|
|
39
40
|
return cleanHTMLToMarkdown(externalHTML);
|
|
40
41
|
}
|
|
@@ -3,10 +3,10 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
|
3
3
|
import { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
4
4
|
|
|
5
5
|
import { PartialBlock } from "../../blocks/defaultBlocks";
|
|
6
|
+
import { customBlocksTestCases } from "../testUtil/cases/customBlocks";
|
|
6
7
|
import { customInlineContentTestCases } from "../testUtil/cases/customInlineContent";
|
|
7
8
|
import { customStylesTestCases } from "../testUtil/cases/customStyles";
|
|
8
9
|
import { defaultSchemaTestCases } from "../testUtil/cases/defaultSchema";
|
|
9
|
-
import { customBlocksTestCases } from "../testUtil/cases/customBlocks";
|
|
10
10
|
import {
|
|
11
11
|
addIdsToBlock,
|
|
12
12
|
partialBlockToBlockForTesting,
|
|
@@ -18,11 +18,7 @@ function validateConversion(
|
|
|
18
18
|
editor: BlockNoteEditor<any, any, any>
|
|
19
19
|
) {
|
|
20
20
|
addIdsToBlock(block);
|
|
21
|
-
const node = blockToNode(
|
|
22
|
-
block,
|
|
23
|
-
editor._tiptapEditor.schema,
|
|
24
|
-
editor.schema.styleSchema
|
|
25
|
-
);
|
|
21
|
+
const node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema);
|
|
26
22
|
|
|
27
23
|
expect(node).toMatchSnapshot();
|
|
28
24
|
|
|
@@ -56,6 +52,13 @@ describe("Test BlockNote-Prosemirror conversion", () => {
|
|
|
56
52
|
|
|
57
53
|
beforeEach(() => {
|
|
58
54
|
editor = testCase.createEditor();
|
|
55
|
+
// Note that we don't necessarily need to mount a root
|
|
56
|
+
// Currently, we do mount to a root so that it reflects the "production" use-case more closely.
|
|
57
|
+
|
|
58
|
+
// However, it would be nice to increased converage and share the same set of tests for these cases:
|
|
59
|
+
// - does render to a root
|
|
60
|
+
// - does not render to a root
|
|
61
|
+
// - runs in server (jsdom) environment using server-util
|
|
59
62
|
editor.mount(div);
|
|
60
63
|
});
|
|
61
64
|
|
|
@@ -33,16 +33,15 @@ async function parseHTMLAndCompareSnapshots(
|
|
|
33
33
|
|
|
34
34
|
(window as any).__TEST_OPTIONS.mockID = 0; // reset id counter
|
|
35
35
|
const htmlNode = nestedListsToBlockNoteStructure(html);
|
|
36
|
-
const tt = editor._tiptapEditor;
|
|
37
36
|
|
|
38
37
|
const slice = view.__parseFromClipboard(
|
|
39
|
-
|
|
38
|
+
editor.prosemirrorView,
|
|
40
39
|
"",
|
|
41
40
|
htmlNode.innerHTML,
|
|
42
41
|
false,
|
|
43
|
-
|
|
42
|
+
editor._tiptapEditor.state.selection.$from
|
|
44
43
|
);
|
|
45
|
-
|
|
44
|
+
editor.dispatch(editor._tiptapEditor.state.tr.replaceSelection(slice));
|
|
46
45
|
|
|
47
46
|
// alternative paste simulation doesn't work in a non-browser vitest env
|
|
48
47
|
// editor._tiptapEditor.view.pasteHTML(html, {
|
|
@@ -5,7 +5,7 @@ exports[`Lift nested lists > Lifts multiple bullet lists 1`] = `
|
|
|
5
5
|
<ul>
|
|
6
6
|
<div>
|
|
7
7
|
<li>Bullet List Item 1</li>
|
|
8
|
-
<div data-node-type
|
|
8
|
+
<div data-node-type="blockGroup">
|
|
9
9
|
<ul>
|
|
10
10
|
<li>Nested Bullet List Item 1</li>
|
|
11
11
|
<li>Nested Bullet List Item 2</li>
|
|
@@ -26,7 +26,7 @@ exports[`Lift nested lists > Lifts multiple bullet lists with content in between
|
|
|
26
26
|
<ul>
|
|
27
27
|
<div>
|
|
28
28
|
<li>Bullet List Item 1</li>
|
|
29
|
-
<div data-node-type
|
|
29
|
+
<div data-node-type="blockGroup">
|
|
30
30
|
<ul>
|
|
31
31
|
<li>Nested Bullet List Item 1</li>
|
|
32
32
|
<li>Nested Bullet List Item 2</li>
|
|
@@ -35,7 +35,7 @@ exports[`Lift nested lists > Lifts multiple bullet lists with content in between
|
|
|
35
35
|
</div>
|
|
36
36
|
<div>
|
|
37
37
|
<li>In between content</li>
|
|
38
|
-
<div data-node-type
|
|
38
|
+
<div data-node-type="blockGroup">
|
|
39
39
|
<ul>
|
|
40
40
|
<li>Nested Bullet List Item 3</li>
|
|
41
41
|
<li>Nested Bullet List Item 4</li>
|
|
@@ -52,7 +52,7 @@ exports[`Lift nested lists > Lifts nested bullet lists 1`] = `
|
|
|
52
52
|
<ul>
|
|
53
53
|
<div>
|
|
54
54
|
<li>Bullet List Item 1</li>
|
|
55
|
-
<div data-node-type
|
|
55
|
+
<div data-node-type="blockGroup">
|
|
56
56
|
<ul>
|
|
57
57
|
<li>Nested Bullet List Item 1</li>
|
|
58
58
|
<li>Nested Bullet List Item 2</li>
|
|
@@ -69,7 +69,7 @@ exports[`Lift nested lists > Lifts nested bullet lists with content after nested
|
|
|
69
69
|
<ul>
|
|
70
70
|
<div>
|
|
71
71
|
<li>Bullet List Item 1</li>
|
|
72
|
-
<div data-node-type
|
|
72
|
+
<div data-node-type="blockGroup">
|
|
73
73
|
<ul>
|
|
74
74
|
<li>Nested Bullet List Item 1</li>
|
|
75
75
|
<li>Nested Bullet List Item 2</li>
|
|
@@ -99,7 +99,7 @@ exports[`Lift nested lists > Lifts nested mixed lists 1`] = `
|
|
|
99
99
|
<ol>
|
|
100
100
|
<div>
|
|
101
101
|
<li>Numbered List Item 1</li>
|
|
102
|
-
<div data-node-type
|
|
102
|
+
<div data-node-type="blockGroup">
|
|
103
103
|
<ul>
|
|
104
104
|
<li>Bullet List Item 1</li>
|
|
105
105
|
<li>Bullet List Item 2</li>
|
|
@@ -116,7 +116,7 @@ exports[`Lift nested lists > Lifts nested numbered lists 1`] = `
|
|
|
116
116
|
<ol>
|
|
117
117
|
<div>
|
|
118
118
|
<li>Numbered List Item 1</li>
|
|
119
|
-
<div data-node-type
|
|
119
|
+
<div data-node-type="blockGroup">
|
|
120
120
|
<ol>
|
|
121
121
|
<li>Nested Numbered List Item 1</li>
|
|
122
122
|
<li>Nested Numbered List Item 2</li>
|
|
@@ -75,7 +75,7 @@ export const createAddFileButton = (
|
|
|
75
75
|
};
|
|
76
76
|
// Opens the file toolbar.
|
|
77
77
|
const addFileButtonClickHandler = () => {
|
|
78
|
-
editor.
|
|
78
|
+
editor.dispatch(
|
|
79
79
|
editor._tiptapEditor.state.tr.setMeta(editor.filePanel!.plugin, {
|
|
80
80
|
block: block,
|
|
81
81
|
})
|
|
@@ -67,12 +67,9 @@ export const defaultBlockToHTML = <
|
|
|
67
67
|
dom: HTMLElement;
|
|
68
68
|
contentDOM?: HTMLElement;
|
|
69
69
|
} => {
|
|
70
|
-
const node = blockToNode(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
editor.schema.styleSchema
|
|
74
|
-
).firstChild!;
|
|
75
|
-
const toDOM = editor._tiptapEditor.schema.nodes[node.type.name].spec.toDOM;
|
|
70
|
+
const node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema)
|
|
71
|
+
.firstChild!;
|
|
72
|
+
const toDOM = editor.pmSchema.nodes[node.type.name].spec.toDOM;
|
|
76
73
|
|
|
77
74
|
if (toDOM === undefined) {
|
|
78
75
|
throw new Error(
|
|
@@ -6,7 +6,13 @@ import {
|
|
|
6
6
|
InlineContentSchema,
|
|
7
7
|
StyleSchema,
|
|
8
8
|
} from "../schema";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Block,
|
|
11
|
+
DefaultBlockSchema,
|
|
12
|
+
defaultBlockSchema,
|
|
13
|
+
defaultInlineContentSchema,
|
|
14
|
+
DefaultInlineContentSchema,
|
|
15
|
+
} from "./defaultBlocks";
|
|
10
16
|
import { defaultProps } from "./defaultProps";
|
|
11
17
|
|
|
12
18
|
export function checkDefaultBlockTypeInSchema<
|
|
@@ -23,6 +29,25 @@ export function checkDefaultBlockTypeInSchema<
|
|
|
23
29
|
);
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
export function checkDefaultInlineContentTypeInSchema<
|
|
33
|
+
InlineContentType extends keyof DefaultInlineContentSchema,
|
|
34
|
+
B extends BlockSchema,
|
|
35
|
+
S extends StyleSchema
|
|
36
|
+
>(
|
|
37
|
+
inlineContentType: InlineContentType,
|
|
38
|
+
editor: BlockNoteEditor<B, any, S>
|
|
39
|
+
): editor is BlockNoteEditor<
|
|
40
|
+
B,
|
|
41
|
+
{ Type: DefaultInlineContentSchema[InlineContentType] },
|
|
42
|
+
S
|
|
43
|
+
> {
|
|
44
|
+
return (
|
|
45
|
+
inlineContentType in editor.schema.inlineContentSchema &&
|
|
46
|
+
editor.schema.inlineContentSchema[inlineContentType] ===
|
|
47
|
+
defaultInlineContentSchema[inlineContentType]
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
export function checkBlockIsDefaultType<
|
|
27
52
|
BlockType extends keyof DefaultBlockSchema,
|
|
28
53
|
I extends InlineContentSchema,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, it } from "vitest";
|
|
2
|
-
import { BlockNoteEditor } from "./BlockNoteEditor";
|
|
3
2
|
import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos";
|
|
3
|
+
import { BlockNoteEditor } from "./BlockNoteEditor";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @vitest-environment jsdom
|
|
@@ -10,3 +10,50 @@ it("creates an editor", () => {
|
|
|
10
10
|
const blockInfo = getBlockInfoFromPos(editor._tiptapEditor.state.doc, 2);
|
|
11
11
|
expect(blockInfo?.contentNode.type.name).toEqual("paragraph");
|
|
12
12
|
});
|
|
13
|
+
|
|
14
|
+
it("immediately replaces doc", async () => {
|
|
15
|
+
const editor = BlockNoteEditor.create();
|
|
16
|
+
const blocks = await editor.tryParseMarkdownToBlocks(
|
|
17
|
+
"This is a normal text\n\n# And this is a large heading"
|
|
18
|
+
);
|
|
19
|
+
editor.replaceBlocks(editor.document, blocks);
|
|
20
|
+
expect(editor.document).toMatchInlineSnapshot(`
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
"children": [],
|
|
24
|
+
"content": [
|
|
25
|
+
{
|
|
26
|
+
"styles": {},
|
|
27
|
+
"text": "This is a normal text",
|
|
28
|
+
"type": "text",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"id": "1",
|
|
32
|
+
"props": {
|
|
33
|
+
"backgroundColor": "default",
|
|
34
|
+
"textAlignment": "left",
|
|
35
|
+
"textColor": "default",
|
|
36
|
+
},
|
|
37
|
+
"type": "paragraph",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"children": [],
|
|
41
|
+
"content": [
|
|
42
|
+
{
|
|
43
|
+
"styles": {},
|
|
44
|
+
"text": "And this is a large heading",
|
|
45
|
+
"type": "text",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
"id": "2",
|
|
49
|
+
"props": {
|
|
50
|
+
"backgroundColor": "default",
|
|
51
|
+
"level": 1,
|
|
52
|
+
"textAlignment": "left",
|
|
53
|
+
"textColor": "default",
|
|
54
|
+
},
|
|
55
|
+
"type": "heading",
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
`);
|
|
59
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EditorOptions, Extension } from "@tiptap/core";
|
|
2
|
-
import { Node } from "prosemirror-model";
|
|
1
|
+
import { EditorOptions, Extension, getSchema } from "@tiptap/core";
|
|
2
|
+
import { Node, Schema } from "prosemirror-model";
|
|
3
3
|
// import "./blocknote.css";
|
|
4
4
|
import * as Y from "yjs";
|
|
5
5
|
import {
|
|
@@ -61,12 +61,13 @@ import {
|
|
|
61
61
|
BlockNoteTipTapEditorOptions,
|
|
62
62
|
} from "./BlockNoteTipTapEditor";
|
|
63
63
|
|
|
64
|
-
// CSS
|
|
65
64
|
import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin";
|
|
66
65
|
import { Dictionary } from "../i18n/dictionary";
|
|
67
66
|
import { en } from "../i18n/locales";
|
|
68
|
-
|
|
69
|
-
import "
|
|
67
|
+
|
|
68
|
+
import { Transaction } from "@tiptap/pm/state";
|
|
69
|
+
import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer";
|
|
70
|
+
import "../style.css";
|
|
70
71
|
|
|
71
72
|
export type BlockNoteEditorOptions<
|
|
72
73
|
BSchema extends BlockSchema,
|
|
@@ -109,7 +110,11 @@ export type BlockNoteEditorOptions<
|
|
|
109
110
|
schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
|
-
*
|
|
113
|
+
* The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
|
|
114
|
+
* This method should set when creating the editor as this is application-specific.
|
|
115
|
+
*
|
|
116
|
+
* `undefined` means the application doesn't support file uploads.
|
|
117
|
+
*
|
|
113
118
|
* @param file The file that should be uploaded.
|
|
114
119
|
* @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
|
|
115
120
|
*/
|
|
@@ -151,6 +156,15 @@ export type BlockNoteEditorOptions<
|
|
|
151
156
|
_tiptapOptions: Partial<EditorOptions>;
|
|
152
157
|
|
|
153
158
|
trailingBlock?: boolean;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Boolean indicating whether the editor is in headless mode.
|
|
162
|
+
* Headless mode means we can use features like importing / exporting blocks,
|
|
163
|
+
* but there's no underlying editor (UI) instantiated.
|
|
164
|
+
*
|
|
165
|
+
* You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
|
|
166
|
+
*/
|
|
167
|
+
_headless: boolean;
|
|
154
168
|
};
|
|
155
169
|
|
|
156
170
|
const blockNoteTipTapOptions = {
|
|
@@ -164,12 +178,44 @@ export class BlockNoteEditor<
|
|
|
164
178
|
ISchema extends InlineContentSchema = DefaultInlineContentSchema,
|
|
165
179
|
SSchema extends StyleSchema = DefaultStyleSchema
|
|
166
180
|
> {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
private readonly _pmSchema: Schema;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Boolean indicating whether the editor is in headless mode.
|
|
185
|
+
* Headless mode means we can use features like importing / exporting blocks,
|
|
186
|
+
* but there's no underlying editor (UI) instantiated.
|
|
187
|
+
*
|
|
188
|
+
* You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
|
|
189
|
+
*/
|
|
190
|
+
public readonly headless: boolean = false;
|
|
191
|
+
|
|
192
|
+
public readonly _tiptapEditor:
|
|
193
|
+
| BlockNoteTipTapEditor & {
|
|
194
|
+
contentComponent: any;
|
|
195
|
+
} = undefined as any; // TODO: Type should actually reflect that it can be `undefined` in headless mode
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Used by React to store a reference to an `ElementRenderer` helper utility to make sure we can render React elements
|
|
199
|
+
* in the correct context (used by `ReactRenderUtil`)
|
|
200
|
+
*/
|
|
201
|
+
public elementRenderer: ((node: any, container: HTMLElement) => void) | null =
|
|
202
|
+
null;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Cache of all blocks. This makes sure we don't have to "recompute" blocks if underlying Prosemirror Nodes haven't changed.
|
|
206
|
+
* This is especially useful when we want to keep track of the same block across multiple operations,
|
|
207
|
+
* with this cache, blocks stay the same object reference (referential equality with ===).
|
|
208
|
+
*/
|
|
170
209
|
public blockCache = new WeakMap<Node, Block<any, any, any>>();
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The dictionary contains translations for the editor.
|
|
213
|
+
*/
|
|
171
214
|
public readonly dictionary: Dictionary;
|
|
172
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor.
|
|
218
|
+
*/
|
|
173
219
|
public readonly schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
|
|
174
220
|
|
|
175
221
|
public readonly blockImplementations: BlockSpecs;
|
|
@@ -198,12 +244,25 @@ export class BlockNoteEditor<
|
|
|
198
244
|
SSchema
|
|
199
245
|
>;
|
|
200
246
|
|
|
247
|
+
/**
|
|
248
|
+
* The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
|
|
249
|
+
* This method should set when creating the editor as this is application-specific.
|
|
250
|
+
*
|
|
251
|
+
* `undefined` means the application doesn't support file uploads.
|
|
252
|
+
*
|
|
253
|
+
* @param file The file that should be uploaded.
|
|
254
|
+
* @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
|
|
255
|
+
*/
|
|
201
256
|
public readonly uploadFile:
|
|
202
257
|
| ((file: File) => Promise<string | Record<string, any>>)
|
|
203
258
|
| undefined;
|
|
204
259
|
|
|
205
260
|
public readonly resolveFileUrl: (url: string) => Promise<string>;
|
|
206
261
|
|
|
262
|
+
public get pmSchema() {
|
|
263
|
+
return this._pmSchema;
|
|
264
|
+
}
|
|
265
|
+
|
|
207
266
|
public static create<
|
|
208
267
|
BSchema extends BlockSchema = DefaultBlockSchema,
|
|
209
268
|
ISchema extends InlineContentSchema = DefaultInlineContentSchema,
|
|
@@ -246,6 +305,7 @@ export class BlockNoteEditor<
|
|
|
246
305
|
const newOptions = {
|
|
247
306
|
defaultStyles: true,
|
|
248
307
|
schema: options.schema || BlockNoteSchema.create(),
|
|
308
|
+
_headless: false,
|
|
249
309
|
...options,
|
|
250
310
|
placeholders: {
|
|
251
311
|
...this.dictionary.placeholders,
|
|
@@ -272,7 +332,6 @@ export class BlockNoteEditor<
|
|
|
272
332
|
const extensions = getBlockNoteExtensions({
|
|
273
333
|
editor: this,
|
|
274
334
|
domAttributes: newOptions.domAttributes || {},
|
|
275
|
-
blockSchema: this.schema.blockSchema,
|
|
276
335
|
blockSpecs: this.schema.blockSpecs,
|
|
277
336
|
styleSpecs: this.schema.styleSpecs,
|
|
278
337
|
inlineContentSpecs: this.schema.inlineContentSpecs,
|
|
@@ -300,6 +359,7 @@ export class BlockNoteEditor<
|
|
|
300
359
|
|
|
301
360
|
this.uploadFile = newOptions.uploadFile;
|
|
302
361
|
this.resolveFileUrl = newOptions.resolveFileUrl || (async (url) => url);
|
|
362
|
+
this.headless = newOptions._headless;
|
|
303
363
|
|
|
304
364
|
if (newOptions.collaboration && newOptions.initialContent) {
|
|
305
365
|
// eslint-disable-next-line no-console
|
|
@@ -354,22 +414,33 @@ export class BlockNoteEditor<
|
|
|
354
414
|
},
|
|
355
415
|
};
|
|
356
416
|
|
|
357
|
-
this.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
417
|
+
if (!this.headless) {
|
|
418
|
+
this._tiptapEditor = new BlockNoteTipTapEditor(
|
|
419
|
+
tiptapOptions,
|
|
420
|
+
this.schema.styleSchema
|
|
421
|
+
) as BlockNoteTipTapEditor & {
|
|
422
|
+
contentComponent: any;
|
|
423
|
+
};
|
|
424
|
+
this._pmSchema = this._tiptapEditor.schema;
|
|
425
|
+
} else {
|
|
426
|
+
// In headless mode, we don't instantiate an underlying TipTap editor,
|
|
427
|
+
// but we still need the schema
|
|
428
|
+
this._pmSchema = getSchema(tiptapOptions.extensions!);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
dispatch(tr: Transaction) {
|
|
433
|
+
this._tiptapEditor.dispatch(tr);
|
|
363
434
|
}
|
|
364
435
|
|
|
365
436
|
/**
|
|
366
437
|
* Mount the editor to a parent DOM element. Call mount(undefined) to clean up
|
|
367
438
|
*
|
|
368
|
-
* @warning Not needed
|
|
439
|
+
* @warning Not needed to call manually when using React, use BlockNoteView to take care of mounting
|
|
369
440
|
*/
|
|
370
|
-
public mount(parentElement?: HTMLElement | null) {
|
|
441
|
+
public mount = (parentElement?: HTMLElement | null) => {
|
|
371
442
|
this._tiptapEditor.mount(parentElement);
|
|
372
|
-
}
|
|
443
|
+
};
|
|
373
444
|
|
|
374
445
|
public get prosemirrorView() {
|
|
375
446
|
return this._tiptapEditor.view;
|
|
@@ -391,7 +462,7 @@ export class BlockNoteEditor<
|
|
|
391
462
|
* @deprecated, use `editor.document` instead
|
|
392
463
|
*/
|
|
393
464
|
public get topLevelBlocks(): Block<BSchema, ISchema, SSchema>[] {
|
|
394
|
-
return this.
|
|
465
|
+
return this.document;
|
|
395
466
|
}
|
|
396
467
|
|
|
397
468
|
/**
|
|
@@ -676,6 +747,12 @@ export class BlockNoteEditor<
|
|
|
676
747
|
* @returns True if the editor is editable, false otherwise.
|
|
677
748
|
*/
|
|
678
749
|
public get isEditable(): boolean {
|
|
750
|
+
if (!this._tiptapEditor) {
|
|
751
|
+
if (!this.headless) {
|
|
752
|
+
throw new Error("no editor, but also not headless?");
|
|
753
|
+
}
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
679
756
|
return this._tiptapEditor.isEditable;
|
|
680
757
|
}
|
|
681
758
|
|
|
@@ -684,6 +761,13 @@ export class BlockNoteEditor<
|
|
|
684
761
|
* @param editable True to make the editor editable, or false to lock it.
|
|
685
762
|
*/
|
|
686
763
|
public set isEditable(editable: boolean) {
|
|
764
|
+
if (!this._tiptapEditor) {
|
|
765
|
+
if (!this.headless) {
|
|
766
|
+
throw new Error("no editor, but also not headless?");
|
|
767
|
+
}
|
|
768
|
+
// not relevant on headless
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
687
771
|
if (this._tiptapEditor.options.editable !== editable) {
|
|
688
772
|
this._tiptapEditor.setEditable(editable);
|
|
689
773
|
}
|
|
@@ -749,7 +833,7 @@ export class BlockNoteEditor<
|
|
|
749
833
|
public insertInlineContent(content: PartialInlineContent<ISchema, SSchema>) {
|
|
750
834
|
const nodes = inlineContentToNodes(
|
|
751
835
|
content,
|
|
752
|
-
this.
|
|
836
|
+
this.pmSchema,
|
|
753
837
|
this.schema.styleSchema
|
|
754
838
|
);
|
|
755
839
|
|
|
@@ -873,10 +957,10 @@ export class BlockNoteEditor<
|
|
|
873
957
|
text = this._tiptapEditor.state.doc.textBetween(from, to);
|
|
874
958
|
}
|
|
875
959
|
|
|
876
|
-
const mark = this.
|
|
960
|
+
const mark = this.pmSchema.mark("link", { href: url });
|
|
877
961
|
|
|
878
|
-
this.
|
|
879
|
-
this._tiptapEditor.
|
|
962
|
+
this.dispatch(
|
|
963
|
+
this._tiptapEditor.state.tr
|
|
880
964
|
.insertText(text, from, to)
|
|
881
965
|
.addMark(from, from + text.length, mark)
|
|
882
966
|
);
|
|
@@ -920,23 +1004,35 @@ export class BlockNoteEditor<
|
|
|
920
1004
|
this._tiptapEditor.commands.liftListItem("blockContainer");
|
|
921
1005
|
}
|
|
922
1006
|
|
|
923
|
-
// TODO: Fix when implementing HTML/Markdown import & export
|
|
924
1007
|
/**
|
|
925
|
-
*
|
|
1008
|
+
* Exports blocks into a simplified HTML string. To better conform to HTML standards, children of blocks which aren't list
|
|
926
1009
|
* items are un-nested in the output HTML.
|
|
1010
|
+
*
|
|
927
1011
|
* @param blocks An array of blocks that should be serialized into HTML.
|
|
928
1012
|
* @returns The blocks, serialized as an HTML string.
|
|
929
1013
|
*/
|
|
930
1014
|
public async blocksToHTMLLossy(
|
|
931
|
-
blocks:
|
|
1015
|
+
blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document
|
|
932
1016
|
): Promise<string> {
|
|
933
|
-
const exporter = createExternalHTMLExporter(
|
|
934
|
-
|
|
935
|
-
this
|
|
936
|
-
);
|
|
937
|
-
return exporter.exportBlocks(blocks);
|
|
1017
|
+
const exporter = createExternalHTMLExporter(this.pmSchema, this);
|
|
1018
|
+
return exporter.exportBlocks(blocks, {});
|
|
938
1019
|
}
|
|
939
1020
|
|
|
1021
|
+
/**
|
|
1022
|
+
* Serializes blocks into an HTML string in the format that would normally be rendered by the editor.
|
|
1023
|
+
*
|
|
1024
|
+
* Use this method if you want to server-side render HTML (for example, a blog post that has been edited in BlockNote)
|
|
1025
|
+
* and serve it to users without loading the editor on the client (i.e.: displaying the blog post)
|
|
1026
|
+
*
|
|
1027
|
+
* @param blocks An array of blocks that should be serialized into HTML.
|
|
1028
|
+
* @returns The blocks, serialized as an HTML string.
|
|
1029
|
+
*/
|
|
1030
|
+
public async blocksToFullHTML(
|
|
1031
|
+
blocks: PartialBlock<BSchema, ISchema, SSchema>[]
|
|
1032
|
+
): Promise<string> {
|
|
1033
|
+
const exporter = createInternalHTMLSerializer(this.pmSchema, this);
|
|
1034
|
+
return exporter.serializeBlocks(blocks, {});
|
|
1035
|
+
}
|
|
940
1036
|
/**
|
|
941
1037
|
* Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and
|
|
942
1038
|
* `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote
|
|
@@ -952,7 +1048,7 @@ export class BlockNoteEditor<
|
|
|
952
1048
|
this.schema.blockSchema,
|
|
953
1049
|
this.schema.inlineContentSchema,
|
|
954
1050
|
this.schema.styleSchema,
|
|
955
|
-
this.
|
|
1051
|
+
this.pmSchema
|
|
956
1052
|
);
|
|
957
1053
|
}
|
|
958
1054
|
|
|
@@ -963,9 +1059,9 @@ export class BlockNoteEditor<
|
|
|
963
1059
|
* @returns The blocks, serialized as a Markdown string.
|
|
964
1060
|
*/
|
|
965
1061
|
public async blocksToMarkdownLossy(
|
|
966
|
-
blocks:
|
|
1062
|
+
blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document
|
|
967
1063
|
): Promise<string> {
|
|
968
|
-
return blocksToMarkdown(blocks, this.
|
|
1064
|
+
return blocksToMarkdown(blocks, this.pmSchema, this, {});
|
|
969
1065
|
}
|
|
970
1066
|
|
|
971
1067
|
/**
|
|
@@ -983,7 +1079,7 @@ export class BlockNoteEditor<
|
|
|
983
1079
|
this.schema.blockSchema,
|
|
984
1080
|
this.schema.inlineContentSchema,
|
|
985
1081
|
this.schema.styleSchema,
|
|
986
|
-
this.
|
|
1082
|
+
this.pmSchema
|
|
987
1083
|
);
|
|
988
1084
|
}
|
|
989
1085
|
|
|
@@ -1008,6 +1104,11 @@ export class BlockNoteEditor<
|
|
|
1008
1104
|
public onChange(
|
|
1009
1105
|
callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void
|
|
1010
1106
|
) {
|
|
1107
|
+
if (this.headless) {
|
|
1108
|
+
// Note: would be nice if this is possible in headless mode as well
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1011
1112
|
const cb = () => {
|
|
1012
1113
|
callback(this);
|
|
1013
1114
|
};
|
|
@@ -1028,6 +1129,10 @@ export class BlockNoteEditor<
|
|
|
1028
1129
|
public onSelectionChange(
|
|
1029
1130
|
callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void
|
|
1030
1131
|
) {
|
|
1132
|
+
if (this.headless) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1031
1136
|
const cb = () => {
|
|
1032
1137
|
callback(this);
|
|
1033
1138
|
};
|
|
@@ -1038,4 +1143,16 @@ export class BlockNoteEditor<
|
|
|
1038
1143
|
this._tiptapEditor.off("selectionUpdate", cb);
|
|
1039
1144
|
};
|
|
1040
1145
|
}
|
|
1146
|
+
|
|
1147
|
+
public openSelectionMenu(triggerCharacter: string) {
|
|
1148
|
+
this.prosemirrorView.focus();
|
|
1149
|
+
this.prosemirrorView.dispatch(
|
|
1150
|
+
this.prosemirrorView.state.tr
|
|
1151
|
+
.scrollIntoView()
|
|
1152
|
+
.setMeta(this.suggestionMenus.plugin, {
|
|
1153
|
+
triggerCharacter: triggerCharacter,
|
|
1154
|
+
fromUserInput: false,
|
|
1155
|
+
})
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1041
1158
|
}
|
|
@@ -40,7 +40,6 @@ export const getBlockNoteExtensions = <
|
|
|
40
40
|
>(opts: {
|
|
41
41
|
editor: BlockNoteEditor<BSchema, I, S>;
|
|
42
42
|
domAttributes: Partial<BlockNoteDOMAttributes>;
|
|
43
|
-
blockSchema: BSchema;
|
|
44
43
|
blockSpecs: BlockSpecs;
|
|
45
44
|
inlineContentSpecs: InlineContentSpecs;
|
|
46
45
|
styleSpecs: StyleSpecs;
|
|
@@ -3,8 +3,8 @@ import { EditorOptions, createDocument } from "@tiptap/core";
|
|
|
3
3
|
import { Editor as TiptapEditor } from "@tiptap/core";
|
|
4
4
|
import { Node } from "@tiptap/pm/model";
|
|
5
5
|
import { EditorView } from "@tiptap/pm/view";
|
|
6
|
-
import { EditorState } from "prosemirror-state";
|
|
7
6
|
|
|
7
|
+
import { EditorState, Transaction } from "@tiptap/pm/state";
|
|
8
8
|
import { blockToNode } from "../api/nodeConversions/nodeConversions";
|
|
9
9
|
import { PartialBlock } from "../blocks/defaultBlocks";
|
|
10
10
|
import { StyleSchema } from "../schema";
|
|
@@ -115,10 +115,13 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
|
|
|
115
115
|
return this._state;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
dispatch(tr: Transaction) {
|
|
119
|
+
if (this.view) {
|
|
120
|
+
this.view.dispatch(tr);
|
|
121
|
+
} else {
|
|
122
|
+
// before view has been initialized
|
|
123
|
+
this._state = this.state.apply(tr);
|
|
124
|
+
}
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
/**
|
|
@@ -164,3 +167,9 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
|
|
|
164
167
|
}
|
|
165
168
|
};
|
|
166
169
|
}
|
|
170
|
+
|
|
171
|
+
(BlockNoteTipTapEditor.prototype as any).createView = () => {
|
|
172
|
+
// no-op
|
|
173
|
+
// Disable default call to `createView` in the Editor constructor.
|
|
174
|
+
// We should call `createView` manually only when a DOM element is available
|
|
175
|
+
};
|