@blocknote/core 0.8.5 → 0.9.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 +705 -572
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +5 -4
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/BlockNoteEditor.ts +13 -17
- package/src/BlockNoteExtensions.ts +42 -27
- package/src/api/blockManipulation/blockManipulation.test.ts +2 -2
- package/src/api/blockManipulation/blockManipulation.ts +1 -1
- package/src/api/formatConversions/formatConversions.test.ts +2 -2
- package/src/api/formatConversions/formatConversions.ts +47 -3
- package/src/api/nodeConversions/nodeConversions.test.ts +6 -6
- package/src/api/nodeConversions/nodeConversions.ts +6 -6
- package/src/editor.module.css +0 -11
- package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +2 -2
- package/src/extensions/Blocks/api/block.ts +55 -22
- package/src/extensions/Blocks/api/blockTypes.ts +22 -3
- package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +27 -5
- package/src/extensions/Blocks/index.ts +7 -12
- package/src/extensions/Blocks/nodes/Block.module.css +1 -1
- package/src/extensions/Blocks/nodes/BlockContainer.ts +20 -19
- package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +20 -2
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +20 -2
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +20 -2
- package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +29 -6
- package/src/extensions/Blocks/nodes/BlockGroup.ts +19 -11
- package/src/extensions/SideMenu/MultipleNodeSelection.ts +3 -3
- package/src/extensions/SideMenu/SideMenuPlugin.ts +9 -9
- package/src/extensions/UniqueID/UniqueID.ts +10 -9
- package/src/index.ts +1 -0
- package/src/shared/EventEmitter.ts +1 -0
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +6 -2
- package/src/shared/utils.ts +4 -0
- package/types/src/BlockNoteEditor.d.ts +4 -10
- package/types/src/BlockNoteExtensions.d.ts +2 -0
- package/types/src/extensions/Blocks/api/block.d.ts +6 -1
- package/types/src/extensions/Blocks/api/blockTypes.d.ts +15 -3
- package/types/src/extensions/Blocks/api/defaultBlocks.d.ts +36 -4
- package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +9 -1
- package/types/src/extensions/Blocks/index.d.ts +4 -1
- package/types/src/extensions/Blocks/nodes/BlockContainer.d.ts +9 -4
- package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +9 -1
- package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +9 -1
- package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +9 -1
- package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +9 -1
- package/types/src/extensions/Blocks/nodes/BlockGroup.d.ts +9 -1
- package/types/src/extensions/Blocks/nodes/TableCell.d.ts +5 -0
- package/types/src/extensions/Blocks/nodes/TableRow.d.ts +5 -0
- package/types/src/extensions/SideMenu/MultipleNodeSelection.d.ts +1 -1
- package/types/src/index.d.ts +1 -0
- package/types/src/shared/utils.d.ts +1 -0
- package/src/node_modules/.vitest/results.json +0 -1
- package/types/src/EventEmitter.d.ts +0 -11
- package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +0 -0
- package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +0 -16
- package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +0 -55
- package/types/src/extensions/DraggableBlocks/MultipleNodeSelection.d.ts +0 -24
- package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +0 -11
- package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +0 -10
- package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +0 -8
- package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +0 -0
- package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +0 -13
- package/types/src/extensions/SlashMenu/index.d.ts +0 -3
- package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +0 -12
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.
|
|
6
|
+
"version": "0.9.2",
|
|
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": "4d18bc8a01e841954672b9e522f20ce12f4ada10"
|
|
113
113
|
}
|
package/src/BlockNoteEditor.ts
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
updateBlock,
|
|
12
12
|
} from "./api/blockManipulation/blockManipulation";
|
|
13
13
|
import {
|
|
14
|
-
HTMLToBlocks,
|
|
15
14
|
blocksToHTML,
|
|
16
15
|
blocksToMarkdown,
|
|
16
|
+
HTMLToBlocks,
|
|
17
17
|
markdownToBlocks,
|
|
18
18
|
} from "./api/formatConversions/formatConversions";
|
|
19
19
|
import {
|
|
@@ -25,6 +25,7 @@ import styles from "./editor.module.css";
|
|
|
25
25
|
import {
|
|
26
26
|
Block,
|
|
27
27
|
BlockIdentifier,
|
|
28
|
+
BlockNoteDOMAttributes,
|
|
28
29
|
BlockSchema,
|
|
29
30
|
PartialBlock,
|
|
30
31
|
} from "./extensions/Blocks/api/blockTypes";
|
|
@@ -48,6 +49,7 @@ import { BaseSlashMenuItem } from "./extensions/SlashMenu/BaseSlashMenuItem";
|
|
|
48
49
|
import { SlashMenuProsemirrorPlugin } from "./extensions/SlashMenu/SlashMenuPlugin";
|
|
49
50
|
import { getDefaultSlashMenuItems } from "./extensions/SlashMenu/defaultSlashMenuItems";
|
|
50
51
|
import { UniqueID } from "./extensions/UniqueID/UniqueID";
|
|
52
|
+
import { mergeCSSClasses } from "./shared/utils";
|
|
51
53
|
|
|
52
54
|
export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
|
|
53
55
|
// TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them.
|
|
@@ -67,11 +69,11 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
|
|
|
67
69
|
*/
|
|
68
70
|
parentElement: HTMLElement;
|
|
69
71
|
/**
|
|
70
|
-
* An object containing attributes that should be added to the editor
|
|
72
|
+
* An object containing attributes that should be added to HTML elements of the editor.
|
|
71
73
|
*
|
|
72
|
-
* @example { class: "my-editor-class" }
|
|
74
|
+
* @example { editor: { class: "my-editor-class" } }
|
|
73
75
|
*/
|
|
74
|
-
|
|
76
|
+
domAttributes: Partial<BlockNoteDOMAttributes>;
|
|
75
77
|
/**
|
|
76
78
|
* A callback function that runs when the editor is ready to be used.
|
|
77
79
|
*/
|
|
@@ -98,12 +100,6 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
|
|
|
98
100
|
* @default true
|
|
99
101
|
*/
|
|
100
102
|
defaultStyles: boolean;
|
|
101
|
-
/**
|
|
102
|
-
* Whether to use the light or dark theme.
|
|
103
|
-
*
|
|
104
|
-
* @default "light"
|
|
105
|
-
*/
|
|
106
|
-
theme: "light" | "dark";
|
|
107
103
|
|
|
108
104
|
/**
|
|
109
105
|
* A list of block types that should be available in the editor.
|
|
@@ -185,6 +181,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
185
181
|
|
|
186
182
|
const extensions = getBlockNoteExtensions<BSchema>({
|
|
187
183
|
editor: this,
|
|
184
|
+
domAttributes: newOptions.domAttributes || {},
|
|
188
185
|
blockSchema: newOptions.blockSchema,
|
|
189
186
|
collaboration: newOptions.collaboration,
|
|
190
187
|
});
|
|
@@ -266,14 +263,13 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
266
263
|
: [...(newOptions._tiptapOptions?.extensions || []), ...extensions],
|
|
267
264
|
editorProps: {
|
|
268
265
|
attributes: {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
class: [
|
|
266
|
+
...newOptions.domAttributes?.editor,
|
|
267
|
+
class: mergeCSSClasses(
|
|
272
268
|
styles.bnEditor,
|
|
273
269
|
styles.bnRoot,
|
|
274
270
|
newOptions.defaultStyles ? styles.defaultStyles : "",
|
|
275
|
-
newOptions.
|
|
276
|
-
|
|
271
|
+
newOptions.domAttributes?.editor?.class || ""
|
|
272
|
+
),
|
|
277
273
|
},
|
|
278
274
|
},
|
|
279
275
|
};
|
|
@@ -357,7 +353,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
357
353
|
*/
|
|
358
354
|
public forEachBlock(
|
|
359
355
|
callback: (block: Block<BSchema>) => boolean,
|
|
360
|
-
reverse
|
|
356
|
+
reverse = false
|
|
361
357
|
): void {
|
|
362
358
|
const blocks = this.topLevelBlocks.slice();
|
|
363
359
|
|
|
@@ -696,7 +692,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
696
692
|
return;
|
|
697
693
|
}
|
|
698
694
|
|
|
699
|
-
|
|
695
|
+
const { from, to } = this._tiptapEditor.state.selection;
|
|
700
696
|
|
|
701
697
|
if (!text) {
|
|
702
698
|
text = this._tiptapEditor.state.doc.textBetween(from, to);
|
|
@@ -19,8 +19,11 @@ import * as Y from "yjs";
|
|
|
19
19
|
import styles from "./editor.module.css";
|
|
20
20
|
import { BackgroundColorExtension } from "./extensions/BackgroundColor/BackgroundColorExtension";
|
|
21
21
|
import { BackgroundColorMark } from "./extensions/BackgroundColor/BackgroundColorMark";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { BlockContainer, BlockGroup, Doc } from "./extensions/Blocks";
|
|
23
|
+
import {
|
|
24
|
+
BlockNoteDOMAttributes,
|
|
25
|
+
BlockSchema,
|
|
26
|
+
} from "./extensions/Blocks/api/blockTypes";
|
|
24
27
|
import { CustomBlockSerializerExtension } from "./extensions/Blocks/api/serialization";
|
|
25
28
|
import blockStyles from "./extensions/Blocks/nodes/Block.module.css";
|
|
26
29
|
import { Placeholder } from "./extensions/Placeholder/PlaceholderExtension";
|
|
@@ -35,6 +38,7 @@ import UniqueID from "./extensions/UniqueID/UniqueID";
|
|
|
35
38
|
*/
|
|
36
39
|
export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
|
|
37
40
|
editor: BlockNoteEditor<BSchema>;
|
|
41
|
+
domAttributes: Partial<BlockNoteDOMAttributes>;
|
|
38
42
|
blockSchema: BSchema;
|
|
39
43
|
collaboration?: {
|
|
40
44
|
fragment: Y.XmlFragment;
|
|
@@ -86,10 +90,19 @@ export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
|
|
|
86
90
|
BackgroundColorExtension,
|
|
87
91
|
TextAlignmentExtension,
|
|
88
92
|
|
|
89
|
-
//
|
|
90
|
-
|
|
93
|
+
// nodes
|
|
94
|
+
Doc,
|
|
95
|
+
BlockContainer.configure({
|
|
96
|
+
domAttributes: opts.domAttributes,
|
|
97
|
+
}),
|
|
98
|
+
BlockGroup.configure({
|
|
99
|
+
domAttributes: opts.domAttributes,
|
|
100
|
+
}),
|
|
91
101
|
...Object.values(opts.blockSchema).map((blockSpec) =>
|
|
92
|
-
blockSpec.node.configure({
|
|
102
|
+
blockSpec.node.configure({
|
|
103
|
+
editor: opts.editor,
|
|
104
|
+
domAttributes: opts.domAttributes,
|
|
105
|
+
})
|
|
93
106
|
),
|
|
94
107
|
CustomBlockSerializerExtension,
|
|
95
108
|
|
|
@@ -105,32 +118,34 @@ export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
|
|
|
105
118
|
fragment: opts.collaboration.fragment,
|
|
106
119
|
})
|
|
107
120
|
);
|
|
108
|
-
|
|
109
|
-
const
|
|
121
|
+
if (opts.collaboration.provider?.awareness) {
|
|
122
|
+
const defaultRender = (user: { color: string; name: string }) => {
|
|
123
|
+
const cursor = document.createElement("span");
|
|
110
124
|
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
cursor.classList.add(styles["collaboration-cursor__caret"]);
|
|
126
|
+
cursor.setAttribute("style", `border-color: ${user.color}`);
|
|
113
127
|
|
|
114
|
-
|
|
128
|
+
const label = document.createElement("span");
|
|
115
129
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
label.classList.add(styles["collaboration-cursor__label"]);
|
|
131
|
+
label.setAttribute("style", `background-color: ${user.color}`);
|
|
132
|
+
label.insertBefore(document.createTextNode(user.name), null);
|
|
119
133
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
const nonbreakingSpace1 = document.createTextNode("\u2060");
|
|
135
|
+
const nonbreakingSpace2 = document.createTextNode("\u2060");
|
|
136
|
+
cursor.insertBefore(nonbreakingSpace1, null);
|
|
137
|
+
cursor.insertBefore(label, null);
|
|
138
|
+
cursor.insertBefore(nonbreakingSpace2, null);
|
|
139
|
+
return cursor;
|
|
140
|
+
};
|
|
141
|
+
ret.push(
|
|
142
|
+
CollaborationCursor.configure({
|
|
143
|
+
user: opts.collaboration.user,
|
|
144
|
+
render: opts.collaboration.renderCursor || defaultRender,
|
|
145
|
+
provider: opts.collaboration.provider,
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
}
|
|
134
149
|
} else {
|
|
135
150
|
// disable history extension when collaboration is enabled as Yjs takes care of undo / redo
|
|
136
151
|
ret.push(History);
|
|
@@ -24,7 +24,7 @@ let insert: (
|
|
|
24
24
|
) => Block<DefaultBlockSchema>[];
|
|
25
25
|
|
|
26
26
|
beforeEach(() => {
|
|
27
|
-
(window as Window & { __TEST_OPTIONS?:
|
|
27
|
+
(window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
|
|
28
28
|
|
|
29
29
|
editor = new BlockNoteEditor();
|
|
30
30
|
|
|
@@ -80,7 +80,7 @@ afterEach(() => {
|
|
|
80
80
|
editor._tiptapEditor.destroy();
|
|
81
81
|
editor = undefined as any;
|
|
82
82
|
|
|
83
|
-
delete (window as Window & { __TEST_OPTIONS?:
|
|
83
|
+
delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
describe("Inserting Blocks with Different Placements", () => {
|
|
@@ -107,7 +107,7 @@ export function removeBlocks(
|
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
if (idsOfBlocksToRemove.size > 0) {
|
|
110
|
-
|
|
110
|
+
const notFoundIds = [...idsOfBlocksToRemove].join("\n");
|
|
111
111
|
|
|
112
112
|
throw Error(
|
|
113
113
|
"Blocks with the following IDs could not be found in the editor: " +
|
|
@@ -579,7 +579,7 @@ function removeInlineContentClass(html: string) {
|
|
|
579
579
|
}
|
|
580
580
|
|
|
581
581
|
beforeEach(() => {
|
|
582
|
-
(window as Window & { __TEST_OPTIONS?:
|
|
582
|
+
(window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
|
|
583
583
|
|
|
584
584
|
editor = new BlockNoteEditor();
|
|
585
585
|
});
|
|
@@ -588,7 +588,7 @@ afterEach(() => {
|
|
|
588
588
|
editor._tiptapEditor.destroy();
|
|
589
589
|
editor = undefined as any;
|
|
590
590
|
|
|
591
|
-
delete (window as Window & { __TEST_OPTIONS?:
|
|
591
|
+
delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
|
|
592
592
|
});
|
|
593
593
|
|
|
594
594
|
describe("Non-Nested Block/HTML/Markdown Conversions", () => {
|
|
@@ -4,7 +4,7 @@ import rehypeRemark from "rehype-remark";
|
|
|
4
4
|
import rehypeStringify from "rehype-stringify";
|
|
5
5
|
import remarkGfm from "remark-gfm";
|
|
6
6
|
import remarkParse from "remark-parse";
|
|
7
|
-
import remarkRehype from "remark-rehype";
|
|
7
|
+
import remarkRehype, { defaultHandlers } from "remark-rehype";
|
|
8
8
|
import remarkStringify from "remark-stringify";
|
|
9
9
|
import { unified } from "unified";
|
|
10
10
|
import { Block, BlockSchema } from "../../extensions/Blocks/api/blockTypes";
|
|
@@ -47,7 +47,7 @@ export async function HTMLToBlocks<BSchema extends BlockSchema>(
|
|
|
47
47
|
htmlNode.innerHTML = html.trim();
|
|
48
48
|
|
|
49
49
|
const parser = DOMParser.fromSchema(schema);
|
|
50
|
-
const parentNode = parser.parse(htmlNode);
|
|
50
|
+
const parentNode = parser.parse(htmlNode); //, { preserveWhitespace: "full" });
|
|
51
51
|
|
|
52
52
|
const blocks: Block<BSchema>[] = [];
|
|
53
53
|
|
|
@@ -73,6 +73,45 @@ export async function blocksToMarkdown<BSchema extends BlockSchema>(
|
|
|
73
73
|
return markdownString.value as string;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// modefied version of https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/code.js
|
|
77
|
+
// that outputs a data-language attribute instead of a CSS class (e.g.: language-typescript)
|
|
78
|
+
function code(state: any, node: any) {
|
|
79
|
+
const value = node.value ? node.value + "\n" : "";
|
|
80
|
+
/** @type {Properties} */
|
|
81
|
+
const properties: any = {};
|
|
82
|
+
|
|
83
|
+
if (node.lang) {
|
|
84
|
+
// changed line
|
|
85
|
+
properties["data-language"] = node.lang;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Create `<code>`.
|
|
89
|
+
/** @type {Element} */
|
|
90
|
+
let result: any = {
|
|
91
|
+
type: "element",
|
|
92
|
+
tagName: "code",
|
|
93
|
+
properties,
|
|
94
|
+
children: [{ type: "text", value }],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (node.meta) {
|
|
98
|
+
result.data = { meta: node.meta };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
state.patch(node, result);
|
|
102
|
+
result = state.applyData(node, result);
|
|
103
|
+
|
|
104
|
+
// Create `<pre>`.
|
|
105
|
+
result = {
|
|
106
|
+
type: "element",
|
|
107
|
+
tagName: "pre",
|
|
108
|
+
properties: {},
|
|
109
|
+
children: [result],
|
|
110
|
+
};
|
|
111
|
+
state.patch(node, result);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
76
115
|
export async function markdownToBlocks<BSchema extends BlockSchema>(
|
|
77
116
|
markdown: string,
|
|
78
117
|
blockSchema: BSchema,
|
|
@@ -81,7 +120,12 @@ export async function markdownToBlocks<BSchema extends BlockSchema>(
|
|
|
81
120
|
const htmlString = await unified()
|
|
82
121
|
.use(remarkParse)
|
|
83
122
|
.use(remarkGfm)
|
|
84
|
-
.use(remarkRehype
|
|
123
|
+
.use(remarkRehype, {
|
|
124
|
+
handlers: {
|
|
125
|
+
...(defaultHandlers as any),
|
|
126
|
+
code,
|
|
127
|
+
},
|
|
128
|
+
})
|
|
85
129
|
.use(rehypeStringify)
|
|
86
130
|
.process(markdown);
|
|
87
131
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Editor } from "@tiptap/core";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import { BlockNoteEditor, PartialBlock } from "../..";
|
|
4
|
-
import UniqueID from "../../extensions/UniqueID/UniqueID";
|
|
5
|
-
import { blockToNode, nodeToBlock } from "./nodeConversions";
|
|
6
|
-
import { partialBlockToBlockForTesting } from "./testUtil";
|
|
7
4
|
import {
|
|
8
|
-
defaultBlockSchema,
|
|
9
5
|
DefaultBlockSchema,
|
|
6
|
+
defaultBlockSchema,
|
|
10
7
|
} from "../../extensions/Blocks/api/defaultBlocks";
|
|
8
|
+
import UniqueID from "../../extensions/UniqueID/UniqueID";
|
|
9
|
+
import { blockToNode, nodeToBlock } from "./nodeConversions";
|
|
10
|
+
import { partialBlockToBlockForTesting } from "./testUtil";
|
|
11
11
|
|
|
12
12
|
let editor: BlockNoteEditor;
|
|
13
13
|
let tt: Editor;
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
(window as Window & { __TEST_OPTIONS?:
|
|
16
|
+
(window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
|
|
17
17
|
|
|
18
18
|
editor = new BlockNoteEditor();
|
|
19
19
|
tt = editor._tiptapEditor;
|
|
@@ -24,7 +24,7 @@ afterEach(() => {
|
|
|
24
24
|
editor = undefined as any;
|
|
25
25
|
tt = undefined as any;
|
|
26
26
|
|
|
27
|
-
delete (window as Window & { __TEST_OPTIONS?:
|
|
27
|
+
delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
describe("Simple ProseMirror Node Conversions", () => {
|
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
Styles,
|
|
17
17
|
ToggledStyle,
|
|
18
18
|
} from "../../extensions/Blocks/api/inlineContentTypes";
|
|
19
|
-
import { getBlockInfoFromPos } from "../../extensions/Blocks/helpers/getBlockInfoFromPos";
|
|
20
19
|
import UniqueID from "../../extensions/UniqueID/UniqueID";
|
|
21
20
|
import { UnreachableCaseError } from "../../shared/utils";
|
|
21
|
+
import { getBlockInfo } from "../../extensions/Blocks/helpers/getBlockInfoFromPos";
|
|
22
22
|
|
|
23
23
|
const toggleStyles = new Set<ToggledStyle>([
|
|
24
24
|
"bold",
|
|
@@ -91,7 +91,7 @@ function styledTextArrayToNodes(
|
|
|
91
91
|
content: string | StyledText[],
|
|
92
92
|
schema: Schema
|
|
93
93
|
): Node[] {
|
|
94
|
-
|
|
94
|
+
const nodes: Node[] = [];
|
|
95
95
|
|
|
96
96
|
if (typeof content === "string") {
|
|
97
97
|
nodes.push(
|
|
@@ -113,7 +113,7 @@ export function inlineContentToNodes(
|
|
|
113
113
|
blockContent: PartialInlineContent[],
|
|
114
114
|
schema: Schema
|
|
115
115
|
): Node[] {
|
|
116
|
-
|
|
116
|
+
const nodes: Node[] = [];
|
|
117
117
|
|
|
118
118
|
for (const content of blockContent) {
|
|
119
119
|
if (content.type === "link") {
|
|
@@ -369,7 +369,7 @@ export function nodeToBlock<BSchema extends BlockSchema>(
|
|
|
369
369
|
return cachedBlock;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
const blockInfo =
|
|
372
|
+
const blockInfo = getBlockInfo(node);
|
|
373
373
|
|
|
374
374
|
let id = blockInfo.id;
|
|
375
375
|
|
|
@@ -380,7 +380,7 @@ export function nodeToBlock<BSchema extends BlockSchema>(
|
|
|
380
380
|
|
|
381
381
|
const props: any = {};
|
|
382
382
|
for (const [attr, value] of Object.entries({
|
|
383
|
-
...
|
|
383
|
+
...node.attrs,
|
|
384
384
|
...blockInfo.contentNode.attrs,
|
|
385
385
|
})) {
|
|
386
386
|
const blockSpec = blockSchema[blockInfo.contentType.name];
|
|
@@ -414,7 +414,7 @@ export function nodeToBlock<BSchema extends BlockSchema>(
|
|
|
414
414
|
const children: Block<BSchema>[] = [];
|
|
415
415
|
for (let i = 0; i < blockInfo.numChildBlocks; i++) {
|
|
416
416
|
children.push(
|
|
417
|
-
nodeToBlock(
|
|
417
|
+
nodeToBlock(node.lastChild!.child(i), blockSchema, blockCache)
|
|
418
418
|
);
|
|
419
419
|
}
|
|
420
420
|
|
package/src/editor.module.css
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
.bnEditor {
|
|
4
4
|
outline: none;
|
|
5
5
|
padding-inline: 54px;
|
|
6
|
-
border-radius: 8px;
|
|
7
6
|
|
|
8
7
|
/* Define a set of colors to be used throughout the app for consistency
|
|
9
8
|
see https://atlassian.design/foundations/color for more info */
|
|
@@ -55,16 +54,6 @@ Tippy popups that are appended to document.body directly
|
|
|
55
54
|
-moz-osx-font-smoothing: grayscale;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
[data-theme="light"] {
|
|
59
|
-
background-color: #FFFFFF;
|
|
60
|
-
color: #3F3F3F;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
[data-theme="dark"] {
|
|
64
|
-
background-color: #1F1F1F;
|
|
65
|
-
color: #CFCFCF;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
57
|
.dragPreview {
|
|
69
58
|
position: absolute;
|
|
70
59
|
top: -1000px;
|
|
@@ -96,7 +96,7 @@ export const PreviousBlockTypePlugin = () => {
|
|
|
96
96
|
const newNodes = findChildren(newState.doc, (node) => node.attrs.id);
|
|
97
97
|
|
|
98
98
|
// Traverses all block containers in the new editor state.
|
|
99
|
-
for (
|
|
99
|
+
for (const node of newNodes) {
|
|
100
100
|
const oldNode = oldNodesById.get(node.node.attrs.id);
|
|
101
101
|
|
|
102
102
|
const oldContentNode = oldNode?.node.firstChild;
|
|
@@ -192,7 +192,7 @@ export const PreviousBlockTypePlugin = () => {
|
|
|
192
192
|
pluginState.currentTransactionOldBlockAttrs[node.attrs.id];
|
|
193
193
|
const decorationAttrs: any = {};
|
|
194
194
|
|
|
195
|
-
for (
|
|
195
|
+
for (const [nodeAttr, val] of Object.entries(prevAttrs)) {
|
|
196
196
|
decorationAttrs["data-prev-" + nodeAttributes[nodeAttr]] =
|
|
197
197
|
val || "none";
|
|
198
198
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Attribute, Node } from "@tiptap/core";
|
|
2
|
-
import { BlockNoteEditor } from "../../..";
|
|
2
|
+
import { BlockNoteDOMAttributes, BlockNoteEditor } from "../../..";
|
|
3
3
|
import styles from "../nodes/Block.module.css";
|
|
4
4
|
import {
|
|
5
5
|
BlockConfig,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
TipTapNode,
|
|
10
10
|
TipTapNodeConfig,
|
|
11
11
|
} from "./blockTypes";
|
|
12
|
+
import { mergeCSSClasses } from "../../../shared/utils";
|
|
12
13
|
|
|
13
14
|
export function camelToDataKebab(str: string): string {
|
|
14
15
|
return "data-" + str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
@@ -124,17 +125,17 @@ export function createBlockSpec<
|
|
|
124
125
|
>(
|
|
125
126
|
blockConfig: BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>
|
|
126
127
|
): BlockSpec<BType, PSchema> {
|
|
127
|
-
const node = createTipTapBlock<
|
|
128
|
+
const node = createTipTapBlock<
|
|
129
|
+
BType,
|
|
130
|
+
{
|
|
131
|
+
editor: BlockNoteEditor<BSchema>;
|
|
132
|
+
domAttributes?: BlockNoteDOMAttributes;
|
|
133
|
+
}
|
|
134
|
+
>({
|
|
128
135
|
name: blockConfig.type,
|
|
129
136
|
content: blockConfig.containsInlineContent ? "inline*" : "",
|
|
130
137
|
selectable: blockConfig.containsInlineContent,
|
|
131
138
|
|
|
132
|
-
addOptions() {
|
|
133
|
-
return {
|
|
134
|
-
editor: undefined,
|
|
135
|
-
};
|
|
136
|
-
},
|
|
137
|
-
|
|
138
139
|
addAttributes() {
|
|
139
140
|
return propsToAttributes(blockConfig);
|
|
140
141
|
},
|
|
@@ -151,8 +152,21 @@ export function createBlockSpec<
|
|
|
151
152
|
return ({ HTMLAttributes, getPos }) => {
|
|
152
153
|
// Create blockContent element
|
|
153
154
|
const blockContent = document.createElement("div");
|
|
154
|
-
//
|
|
155
|
-
|
|
155
|
+
// Add custom HTML attributes
|
|
156
|
+
const blockContentDOMAttributes =
|
|
157
|
+
this.options.domAttributes?.blockContent || {};
|
|
158
|
+
for (const [attribute, value] of Object.entries(
|
|
159
|
+
blockContentDOMAttributes
|
|
160
|
+
)) {
|
|
161
|
+
if (attribute !== "class") {
|
|
162
|
+
blockContent.setAttribute(attribute, value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Set blockContent & custom classes
|
|
166
|
+
blockContent.className = mergeCSSClasses(
|
|
167
|
+
styles.blockContent,
|
|
168
|
+
blockContentDOMAttributes.class
|
|
169
|
+
);
|
|
156
170
|
// Add blockContent HTML attribute
|
|
157
171
|
blockContent.setAttribute("data-content-type", blockConfig.type);
|
|
158
172
|
// Add props as HTML attributes in kebab-case with "data-" prefix
|
|
@@ -186,13 +200,24 @@ export function createBlockSpec<
|
|
|
186
200
|
|
|
187
201
|
// Render elements
|
|
188
202
|
const rendered = blockConfig.render(block as any, editor);
|
|
189
|
-
// Add
|
|
203
|
+
// Add HTML attributes to contentDOM
|
|
190
204
|
if ("contentDOM" in rendered) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
205
|
+
const inlineContentDOMAttributes =
|
|
206
|
+
this.options.domAttributes?.inlineContent || {};
|
|
207
|
+
// Add custom HTML attributes
|
|
208
|
+
for (const [attribute, value] of Object.entries(
|
|
209
|
+
inlineContentDOMAttributes
|
|
210
|
+
)) {
|
|
211
|
+
if (attribute !== "class") {
|
|
212
|
+
rendered.contentDOM.setAttribute(attribute, value);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Merge existing classes with inlineContent & custom classes
|
|
216
|
+
rendered.contentDOM.className = mergeCSSClasses(
|
|
217
|
+
rendered.contentDOM.className,
|
|
218
|
+
styles.inlineContent,
|
|
219
|
+
inlineContentDOMAttributes.class
|
|
220
|
+
);
|
|
196
221
|
}
|
|
197
222
|
// Add elements to blockContent
|
|
198
223
|
blockContent.appendChild(rendered.dom);
|
|
@@ -210,20 +235,28 @@ export function createBlockSpec<
|
|
|
210
235
|
});
|
|
211
236
|
|
|
212
237
|
return {
|
|
213
|
-
node: node
|
|
238
|
+
node: node as TipTapNode<BType>,
|
|
214
239
|
propSchema: blockConfig.propSchema,
|
|
215
240
|
};
|
|
216
241
|
}
|
|
217
242
|
|
|
218
|
-
export function createTipTapBlock<
|
|
219
|
-
|
|
220
|
-
|
|
243
|
+
export function createTipTapBlock<
|
|
244
|
+
Type extends string,
|
|
245
|
+
Options extends {
|
|
246
|
+
domAttributes?: BlockNoteDOMAttributes;
|
|
247
|
+
} = {
|
|
248
|
+
domAttributes?: BlockNoteDOMAttributes;
|
|
249
|
+
},
|
|
250
|
+
Storage = any
|
|
251
|
+
>(
|
|
252
|
+
config: TipTapNodeConfig<Type, Options, Storage>
|
|
253
|
+
): TipTapNode<Type, Options, Storage> {
|
|
221
254
|
// Type cast is needed as Node.name is mutable, though there is basically no
|
|
222
255
|
// reason to change it after creation. Alternative is to wrap Node in a new
|
|
223
256
|
// class, which I don't think is worth it since we'd only be changing 1
|
|
224
257
|
// attribute to be read only.
|
|
225
|
-
return Node.create({
|
|
258
|
+
return Node.create<Options, Storage>({
|
|
226
259
|
...config,
|
|
227
260
|
group: "blockContent",
|
|
228
|
-
}) as TipTapNode<Type>;
|
|
261
|
+
}) as TipTapNode<Type, Options, Storage>;
|
|
229
262
|
}
|
|
@@ -4,13 +4,28 @@ import { BlockNoteEditor } from "../../../BlockNoteEditor";
|
|
|
4
4
|
import { InlineContent, PartialInlineContent } from "./inlineContentTypes";
|
|
5
5
|
import { DefaultBlockSchema } from "./defaultBlocks";
|
|
6
6
|
|
|
7
|
+
export type BlockNoteDOMElement =
|
|
8
|
+
| "editor"
|
|
9
|
+
| "blockContainer"
|
|
10
|
+
| "blockGroup"
|
|
11
|
+
| "blockContent"
|
|
12
|
+
| "inlineContent";
|
|
13
|
+
|
|
14
|
+
export type BlockNoteDOMAttributes = Partial<{
|
|
15
|
+
[DOMElement in BlockNoteDOMElement]: Record<string, string>;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
7
18
|
// A configuration for a TipTap node, but with stricter type constraints on the
|
|
8
19
|
// "name" and "group" properties. The "name" property is now always a string
|
|
9
20
|
// literal type, and the "blockGroup" property cannot be configured as it should
|
|
10
21
|
// always be "blockContent". Used as the parameter in `createTipTapNode`.
|
|
11
22
|
export type TipTapNodeConfig<
|
|
12
23
|
Name extends string,
|
|
13
|
-
Options
|
|
24
|
+
Options extends {
|
|
25
|
+
domAttributes?: BlockNoteDOMAttributes;
|
|
26
|
+
} = {
|
|
27
|
+
domAttributes?: BlockNoteDOMAttributes;
|
|
28
|
+
},
|
|
14
29
|
Storage = any
|
|
15
30
|
> = {
|
|
16
31
|
[K in keyof NodeConfig<Options, Storage>]: K extends "name"
|
|
@@ -25,7 +40,11 @@ export type TipTapNodeConfig<
|
|
|
25
40
|
// "blockGroup" property is now "blockContent". Returned by `createTipTapNode`.
|
|
26
41
|
export type TipTapNode<
|
|
27
42
|
Name extends string,
|
|
28
|
-
Options
|
|
43
|
+
Options extends {
|
|
44
|
+
domAttributes?: BlockNoteDOMAttributes;
|
|
45
|
+
} = {
|
|
46
|
+
domAttributes?: BlockNoteDOMAttributes;
|
|
47
|
+
},
|
|
29
48
|
Storage = any
|
|
30
49
|
> = Node<Options, Storage> & {
|
|
31
50
|
name: Name;
|
|
@@ -104,7 +123,7 @@ export type BlockConfig<
|
|
|
104
123
|
// allowing for more advanced custom blocks.
|
|
105
124
|
export type BlockSpec<Type extends string, PSchema extends PropSchema> = {
|
|
106
125
|
readonly propSchema: PSchema;
|
|
107
|
-
node: TipTapNode<Type>;
|
|
126
|
+
node: TipTapNode<Type, any>;
|
|
108
127
|
};
|
|
109
128
|
|
|
110
129
|
// Utility type. For a given object block schema, ensures that the key of each
|