@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.
Files changed (65) hide show
  1. package/dist/blocknote.js +705 -572
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +5 -4
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +2 -2
  7. package/src/BlockNoteEditor.ts +13 -17
  8. package/src/BlockNoteExtensions.ts +42 -27
  9. package/src/api/blockManipulation/blockManipulation.test.ts +2 -2
  10. package/src/api/blockManipulation/blockManipulation.ts +1 -1
  11. package/src/api/formatConversions/formatConversions.test.ts +2 -2
  12. package/src/api/formatConversions/formatConversions.ts +47 -3
  13. package/src/api/nodeConversions/nodeConversions.test.ts +6 -6
  14. package/src/api/nodeConversions/nodeConversions.ts +6 -6
  15. package/src/editor.module.css +0 -11
  16. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +2 -2
  17. package/src/extensions/Blocks/api/block.ts +55 -22
  18. package/src/extensions/Blocks/api/blockTypes.ts +22 -3
  19. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +27 -5
  20. package/src/extensions/Blocks/index.ts +7 -12
  21. package/src/extensions/Blocks/nodes/Block.module.css +1 -1
  22. package/src/extensions/Blocks/nodes/BlockContainer.ts +20 -19
  23. package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +20 -2
  24. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +20 -2
  25. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +20 -2
  26. package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +29 -6
  27. package/src/extensions/Blocks/nodes/BlockGroup.ts +19 -11
  28. package/src/extensions/SideMenu/MultipleNodeSelection.ts +3 -3
  29. package/src/extensions/SideMenu/SideMenuPlugin.ts +9 -9
  30. package/src/extensions/UniqueID/UniqueID.ts +10 -9
  31. package/src/index.ts +1 -0
  32. package/src/shared/EventEmitter.ts +1 -0
  33. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +6 -2
  34. package/src/shared/utils.ts +4 -0
  35. package/types/src/BlockNoteEditor.d.ts +4 -10
  36. package/types/src/BlockNoteExtensions.d.ts +2 -0
  37. package/types/src/extensions/Blocks/api/block.d.ts +6 -1
  38. package/types/src/extensions/Blocks/api/blockTypes.d.ts +15 -3
  39. package/types/src/extensions/Blocks/api/defaultBlocks.d.ts +36 -4
  40. package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +9 -1
  41. package/types/src/extensions/Blocks/index.d.ts +4 -1
  42. package/types/src/extensions/Blocks/nodes/BlockContainer.d.ts +9 -4
  43. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +9 -1
  44. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +9 -1
  45. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +9 -1
  46. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +9 -1
  47. package/types/src/extensions/Blocks/nodes/BlockGroup.d.ts +9 -1
  48. package/types/src/extensions/Blocks/nodes/TableCell.d.ts +5 -0
  49. package/types/src/extensions/Blocks/nodes/TableRow.d.ts +5 -0
  50. package/types/src/extensions/SideMenu/MultipleNodeSelection.d.ts +1 -1
  51. package/types/src/index.d.ts +1 -0
  52. package/types/src/shared/utils.d.ts +1 -0
  53. package/src/node_modules/.vitest/results.json +0 -1
  54. package/types/src/EventEmitter.d.ts +0 -11
  55. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +0 -0
  56. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +0 -16
  57. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +0 -55
  58. package/types/src/extensions/DraggableBlocks/MultipleNodeSelection.d.ts +0 -24
  59. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +0 -11
  60. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +0 -10
  61. package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +0 -8
  62. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +0 -0
  63. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +0 -13
  64. package/types/src/extensions/SlashMenu/index.d.ts +0 -3
  65. 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.8.5",
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": "5bdbc6a60d4142a6a6a5d85cc8f8a74fdbecf78f"
112
+ "gitHead": "4d18bc8a01e841954672b9e522f20ce12f4ada10"
113
113
  }
@@ -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's HTML element.
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
- editorDOMAttributes: Record<string, string>;
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
- "data-theme": options.theme || "light",
270
- ...(newOptions.editorDOMAttributes || {}),
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.editorDOMAttributes?.class || "",
276
- ].join(" "),
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: boolean = false
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
- let { from, to } = this._tiptapEditor.state.selection;
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 { blocks } from "./extensions/Blocks";
23
- import { BlockSchema } from "./extensions/Blocks/api/blockTypes";
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
- // custom blocks:
90
- ...blocks,
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({ editor: opts.editor })
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
- const defaultRender = (user: { color: string; name: string }) => {
109
- const cursor = document.createElement("span");
121
+ if (opts.collaboration.provider?.awareness) {
122
+ const defaultRender = (user: { color: string; name: string }) => {
123
+ const cursor = document.createElement("span");
110
124
 
111
- cursor.classList.add(styles["collaboration-cursor__caret"]);
112
- cursor.setAttribute("style", `border-color: ${user.color}`);
125
+ cursor.classList.add(styles["collaboration-cursor__caret"]);
126
+ cursor.setAttribute("style", `border-color: ${user.color}`);
113
127
 
114
- const label = document.createElement("span");
128
+ const label = document.createElement("span");
115
129
 
116
- label.classList.add(styles["collaboration-cursor__label"]);
117
- label.setAttribute("style", `background-color: ${user.color}`);
118
- label.insertBefore(document.createTextNode(user.name), null);
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
- const nonbreakingSpace1 = document.createTextNode("\u2060");
121
- const nonbreakingSpace2 = document.createTextNode("\u2060");
122
- cursor.insertBefore(nonbreakingSpace1, null);
123
- cursor.insertBefore(label, null);
124
- cursor.insertBefore(nonbreakingSpace2, null);
125
- return cursor;
126
- };
127
- ret.push(
128
- CollaborationCursor.configure({
129
- user: opts.collaboration.user,
130
- render: opts.collaboration.renderCursor || defaultRender,
131
- provider: opts.collaboration.provider,
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?: {} }).__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?: {} }).__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
- let notFoundIds = [...idsOfBlocksToRemove].join("\n");
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?: {} }).__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?: {} }).__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?: {} }).__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?: {} }).__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
- let nodes: Node[] = [];
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
- let nodes: Node[] = [];
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 = getBlockInfoFromPos(node, 0)!;
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
- ...blockInfo.node.attrs,
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(blockInfo.node.lastChild!.child(i), blockSchema, blockCache)
417
+ nodeToBlock(node.lastChild!.child(i), blockSchema, blockCache)
418
418
  );
419
419
  }
420
420
 
@@ -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 (let node of newNodes) {
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 (let [nodeAttr, val] of Object.entries(prevAttrs)) {
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<BType>({
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
- // Sets blockContent class
155
- blockContent.className = styles.blockContent;
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 inlineContent class to inline content
203
+ // Add HTML attributes to contentDOM
190
204
  if ("contentDOM" in rendered) {
191
- rendered.contentDOM.className = `${
192
- rendered.contentDOM.className
193
- ? rendered.contentDOM.className + " "
194
- : ""
195
- }${styles.inlineContent}`;
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<Type extends string>(
219
- config: TipTapNodeConfig<Type>
220
- ): TipTapNode<Type> {
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 = any,
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 = any,
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