@blocknote/core 0.9.0 → 0.9.3

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 (44) hide show
  1. package/dist/blocknote.js +487 -431
  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/package.json +2 -2
  6. package/src/BlockNoteEditor.ts +2 -2
  7. package/src/BlockNoteExtensions.ts +24 -22
  8. package/src/api/blockManipulation/blockManipulation.test.ts +2 -2
  9. package/src/api/blockManipulation/blockManipulation.ts +1 -1
  10. package/src/api/formatConversions/formatConversions.test.ts +2 -2
  11. package/src/api/formatConversions/formatConversions.ts +47 -3
  12. package/src/api/nodeConversions/nodeConversions.test.ts +6 -6
  13. package/src/api/nodeConversions/nodeConversions.ts +6 -6
  14. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +2 -2
  15. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +27 -5
  16. package/src/extensions/Blocks/nodes/BlockContainer.ts +41 -17
  17. package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +2 -0
  18. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +2 -0
  19. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +2 -0
  20. package/src/extensions/SideMenu/MultipleNodeSelection.ts +3 -3
  21. package/src/extensions/SideMenu/SideMenuPlugin.ts +9 -9
  22. package/src/extensions/TrailingNode/TrailingNodeExtension.ts +13 -1
  23. package/src/extensions/UniqueID/UniqueID.ts +10 -9
  24. package/src/shared/EventEmitter.ts +1 -0
  25. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +6 -2
  26. package/types/src/extensions/Blocks/NonEditableBlockPlugin.d.ts +2 -0
  27. package/types/src/extensions/Blocks/api/defaultProps.d.ts +14 -0
  28. package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +9 -1
  29. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/Image.d.ts +6 -0
  30. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.d.ts +37 -0
  31. package/types/src/extensions/ImageToolbar/ImageToolbarPlugin.d.ts +36 -0
  32. package/types/src/extensions/SideMenu/MultipleNodeSelection.d.ts +1 -1
  33. package/types/src/EventEmitter.d.ts +0 -11
  34. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +0 -0
  35. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +0 -16
  36. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +0 -55
  37. package/types/src/extensions/DraggableBlocks/MultipleNodeSelection.d.ts +0 -24
  38. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +0 -11
  39. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +0 -10
  40. package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +0 -8
  41. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +0 -0
  42. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +0 -13
  43. package/types/src/extensions/SlashMenu/index.d.ts +0 -3
  44. 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.9.0",
6
+ "version": "0.9.3",
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": "75d9591eef6f06f78bdd8dadc32d0ee66c57c4d2"
112
+ "gitHead": "3a3ca4dfc448556f9796ce884b6ba61f036dfe1a"
113
113
  }
@@ -353,7 +353,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
353
353
  */
354
354
  public forEachBlock(
355
355
  callback: (block: Block<BSchema>) => boolean,
356
- reverse: boolean = false
356
+ reverse = false
357
357
  ): void {
358
358
  const blocks = this.topLevelBlocks.slice();
359
359
 
@@ -692,7 +692,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
692
692
  return;
693
693
  }
694
694
 
695
- let { from, to } = this._tiptapEditor.state.selection;
695
+ const { from, to } = this._tiptapEditor.state.selection;
696
696
 
697
697
  if (!text) {
698
698
  text = this._tiptapEditor.state.doc.textBetween(from, to);
@@ -118,32 +118,34 @@ export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
118
118
  fragment: opts.collaboration.fragment,
119
119
  })
120
120
  );
121
- const defaultRender = (user: { color: string; name: string }) => {
122
- 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");
123
124
 
124
- cursor.classList.add(styles["collaboration-cursor__caret"]);
125
- cursor.setAttribute("style", `border-color: ${user.color}`);
125
+ cursor.classList.add(styles["collaboration-cursor__caret"]);
126
+ cursor.setAttribute("style", `border-color: ${user.color}`);
126
127
 
127
- const label = document.createElement("span");
128
+ const label = document.createElement("span");
128
129
 
129
- label.classList.add(styles["collaboration-cursor__label"]);
130
- label.setAttribute("style", `background-color: ${user.color}`);
131
- 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);
132
133
 
133
- const nonbreakingSpace1 = document.createTextNode("\u2060");
134
- const nonbreakingSpace2 = document.createTextNode("\u2060");
135
- cursor.insertBefore(nonbreakingSpace1, null);
136
- cursor.insertBefore(label, null);
137
- cursor.insertBefore(nonbreakingSpace2, null);
138
- return cursor;
139
- };
140
- ret.push(
141
- CollaborationCursor.configure({
142
- user: opts.collaboration.user,
143
- render: opts.collaboration.renderCursor || defaultRender,
144
- provider: opts.collaboration.provider,
145
- })
146
- );
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
+ }
147
149
  } else {
148
150
  // disable history extension when collaboration is enabled as Yjs takes care of undo / redo
149
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
 
@@ -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,16 +1,40 @@
1
1
  import { Node, NodeType } from "prosemirror-model";
2
2
 
3
- export type BlockInfo = {
3
+ export type BlockInfoWithoutPositions = {
4
4
  id: string;
5
5
  node: Node;
6
6
  contentNode: Node;
7
7
  contentType: NodeType;
8
8
  numChildBlocks: number;
9
+ };
10
+
11
+ export type BlockInfo = BlockInfoWithoutPositions & {
9
12
  startPos: number;
10
13
  endPos: number;
11
14
  depth: number;
12
15
  };
13
16
 
17
+ /**
18
+ * Helper function for `getBlockInfoFromPos`, returns information regarding
19
+ * provided blockContainer node.
20
+ * @param blockContainer The blockContainer node to retrieve info for.
21
+ */
22
+ export function getBlockInfo(blockContainer: Node): BlockInfoWithoutPositions {
23
+ const id = blockContainer.attrs["id"];
24
+ const contentNode = blockContainer.firstChild!;
25
+ const contentType = contentNode.type;
26
+ const numChildBlocks =
27
+ blockContainer.childCount === 2 ? blockContainer.lastChild!.childCount : 0;
28
+
29
+ return {
30
+ id,
31
+ node: blockContainer,
32
+ contentNode,
33
+ contentType,
34
+ numChildBlocks,
35
+ };
36
+ }
37
+
14
38
  /**
15
39
  * Retrieves information regarding the nearest blockContainer node in a
16
40
  * ProseMirror doc, relative to a position.
@@ -56,6 +80,7 @@ export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo {
56
80
  let node = $pos.node(maxDepth);
57
81
  let depth = maxDepth;
58
82
 
83
+ // eslint-disable-next-line no-constant-condition
59
84
  while (true) {
60
85
  if (depth < 0) {
61
86
  throw new Error(
@@ -71,10 +96,7 @@ export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo {
71
96
  node = $pos.node(depth);
72
97
  }
73
98
 
74
- const id = node.attrs["id"];
75
- const contentNode = node.firstChild!;
76
- const contentType = contentNode.type;
77
- const numChildBlocks = node.childCount === 2 ? node.lastChild!.childCount : 0;
99
+ const { id, contentNode, contentType, numChildBlocks } = getBlockInfo(node);
78
100
 
79
101
  const startPos = $pos.start(depth);
80
102
  const endPos = $pos.end(depth);
@@ -6,15 +6,15 @@ import {
6
6
  inlineContentToNodes,
7
7
  } from "../../../api/nodeConversions/nodeConversions";
8
8
 
9
- import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos";
10
- import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
11
- import styles from "./Block.module.css";
12
- import BlockAttributes from "./BlockAttributes";
13
9
  import {
14
10
  BlockNoteDOMAttributes,
15
11
  BlockSchema,
16
12
  PartialBlock,
17
13
  } from "../api/blockTypes";
14
+ import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos";
15
+ import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
16
+ import styles from "./Block.module.css";
17
+ import BlockAttributes from "./BlockAttributes";
18
18
  import { mergeCSSClasses } from "../../../shared/utils";
19
19
 
20
20
  declare module "@tiptap/core" {
@@ -60,7 +60,7 @@ export const BlockContainer = Node.create<{
60
60
  }
61
61
 
62
62
  const attrs: Record<string, string> = {};
63
- for (let [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
63
+ for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
64
64
  if (element.getAttribute(HTMLAttr)) {
65
65
  attrs[nodeAttr] = element.getAttribute(HTMLAttr)!;
66
66
  }
@@ -192,18 +192,42 @@ export const BlockContainer = Node.create<{
192
192
  );
193
193
  }
194
194
 
195
- // Changes the blockContent node type and adds the provided props as attributes. Also preserves all existing
196
- // attributes that are compatible with the new type.
197
- state.tr.setNodeMarkup(
198
- startPos,
199
- block.type === undefined
200
- ? undefined
201
- : state.schema.nodes[block.type],
202
- {
203
- ...contentNode.attrs,
204
- ...block.props,
205
- }
206
- );
195
+ // Since some block types contain inline content and others don't,
196
+ // we either need to call setNodeMarkup to just update type &
197
+ // attributes, or replaceWith to replace the whole blockContent.
198
+ const oldType = contentNode.type.name;
199
+ const newType = block.type || oldType;
200
+
201
+ const oldContentType = state.schema.nodes[oldType].spec.content;
202
+ const newContentType = state.schema.nodes[newType].spec.content;
203
+
204
+ if (oldContentType === "inline*" && newContentType === "") {
205
+ // Replaces the blockContent node with one of the new type and
206
+ // adds the provided props as attributes. Also preserves all
207
+ // existing attributes that are compatible with the new type.
208
+ state.tr.replaceWith(
209
+ startPos,
210
+ endPos,
211
+ state.schema.nodes[newType].create({
212
+ ...contentNode.attrs,
213
+ ...block.props,
214
+ })
215
+ );
216
+ } else {
217
+ // Changes the blockContent node type and adds the provided props
218
+ // as attributes. Also preserves all existing attributes that are
219
+ // compatible with the new type.
220
+ state.tr.setNodeMarkup(
221
+ startPos,
222
+ block.type === undefined
223
+ ? undefined
224
+ : state.schema.nodes[block.type],
225
+ {
226
+ ...contentNode.attrs,
227
+ ...block.props,
228
+ }
229
+ );
230
+ }
207
231
 
208
232
  // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing
209
233
  // attributes.
@@ -73,6 +73,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
73
73
  return [
74
74
  "div",
75
75
  mergeAttributes(HTMLAttributes, {
76
+ ...blockContentDOMAttributes,
76
77
  class: mergeCSSClasses(
77
78
  styles.blockContent,
78
79
  blockContentDOMAttributes.class
@@ -82,6 +83,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
82
83
  [
83
84
  "h" + node.attrs.level,
84
85
  {
86
+ ...inlineContentDOMAttributes,
85
87
  class: mergeCSSClasses(
86
88
  styles.inlineContent,
87
89
  inlineContentDOMAttributes.class
@@ -91,6 +91,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
91
91
  return [
92
92
  "div",
93
93
  mergeAttributes(HTMLAttributes, {
94
+ ...blockContentDOMAttributes,
94
95
  class: mergeCSSClasses(
95
96
  styles.blockContent,
96
97
  blockContentDOMAttributes.class
@@ -100,6 +101,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
100
101
  [
101
102
  "p",
102
103
  {
104
+ ...inlineContentDOMAttributes,
103
105
  class: mergeCSSClasses(
104
106
  styles.inlineContent,
105
107
  inlineContentDOMAttributes.class
@@ -115,6 +115,7 @@ export const NumberedListItemBlockContent =
115
115
  return [
116
116
  "div",
117
117
  mergeAttributes(HTMLAttributes, {
118
+ ...blockContentDOMAttributes,
118
119
  class: mergeCSSClasses(
119
120
  styles.blockContent,
120
121
  blockContentDOMAttributes.class
@@ -126,6 +127,7 @@ export const NumberedListItemBlockContent =
126
127
  [
127
128
  "p",
128
129
  {
130
+ ...inlineContentDOMAttributes,
129
131
  class: mergeCSSClasses(
130
132
  styles.inlineContent,
131
133
  inlineContentDOMAttributes.class
@@ -1,5 +1,5 @@
1
- import { Selection } from "prosemirror-state";
2
1
  import { Fragment, Node, ResolvedPos, Slice } from "prosemirror-model";
2
+ import { Selection } from "prosemirror-state";
3
3
  import { Mappable } from "prosemirror-transform";
4
4
 
5
5
  /**
@@ -64,8 +64,8 @@ export class MultipleNodeSelection extends Selection {
64
64
  }
65
65
 
66
66
  map(doc: Node, mapping: Mappable): Selection {
67
- let fromResult = mapping.mapResult(this.from);
68
- let toResult = mapping.mapResult(this.to);
67
+ const fromResult = mapping.mapResult(this.from);
68
+ const toResult = mapping.mapResult(this.to);
69
69
 
70
70
  if (toResult.deleted) {
71
71
  return Selection.near(doc.resolve(fromResult.pos));
@@ -32,7 +32,7 @@ function getDraggableBlockFromCoords(
32
32
  return undefined;
33
33
  }
34
34
 
35
- let pos = view.posAtCoords(coords);
35
+ const pos = view.posAtCoords(coords);
36
36
  if (!pos) {
37
37
  return undefined;
38
38
  }
@@ -61,12 +61,12 @@ function blockPositionFromCoords(
61
61
  coords: { left: number; top: number },
62
62
  view: EditorView
63
63
  ) {
64
- let block = getDraggableBlockFromCoords(coords, view);
64
+ const block = getDraggableBlockFromCoords(coords, view);
65
65
 
66
66
  if (block && block.node.nodeType === 1) {
67
67
  // TODO: this uses undocumented PM APIs? do we need this / let's add docs?
68
68
  const docView = (view as any).docView;
69
- let desc = docView.nearestDesc(block.node, true);
69
+ const desc = docView.nearestDesc(block.node, true);
70
70
  if (!desc || desc === docView) {
71
71
  return null;
72
72
  }
@@ -186,12 +186,12 @@ function dragStart(
186
186
 
187
187
  const editorBoundingBox = view.dom.getBoundingClientRect();
188
188
 
189
- let coords = {
189
+ const coords = {
190
190
  left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor
191
191
  top: e.clientY,
192
192
  };
193
193
 
194
- let pos = blockPositionFromCoords(coords, view);
194
+ const pos = blockPositionFromCoords(coords, view);
195
195
  if (pos != null) {
196
196
  const selection = view.state.selection;
197
197
  const doc = view.state.doc;
@@ -215,8 +215,8 @@ function dragStart(
215
215
  setDragImage(view, pos);
216
216
  }
217
217
 
218
- let slice = view.state.selection.content();
219
- let { dom, text } = serializeForClipboard(view, slice);
218
+ const slice = view.state.selection.content();
219
+ const { dom, text } = serializeForClipboard(view, slice);
220
220
 
221
221
  e.dataTransfer.clearData();
222
222
  e.dataTransfer.setData("text/html", dom.innerHTML);
@@ -288,7 +288,7 @@ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
288
288
  return;
289
289
  }
290
290
 
291
- let pos = this.pmView.posAtCoords({
291
+ const pos = this.pmView.posAtCoords({
292
292
  left: event.clientX,
293
293
  top: event.clientY,
294
294
  });
@@ -319,7 +319,7 @@ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
319
319
  if ((event as any).synthetic || !this.isDragging) {
320
320
  return;
321
321
  }
322
- let pos = this.pmView.posAtCoords({
322
+ const pos = this.pmView.posAtCoords({
323
323
  left: event.clientX,
324
324
  top: event.clientY,
325
325
  });
@@ -64,7 +64,19 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
64
64
  if (!lastNode || lastNode.type.name !== "blockContainer") {
65
65
  throw new Error("Expected blockContainer");
66
66
  }
67
- return lastNode.nodeSize > 4; // empty <block><content/></block> is length 4
67
+
68
+ const lastContentNode = lastNode.firstChild;
69
+
70
+ if (!lastContentNode) {
71
+ throw new Error("Expected blockContent");
72
+ }
73
+
74
+ // If last node is not empty (size > 4) or it doesn't contain
75
+ // inline content, we need to add a trailing node.
76
+ return (
77
+ lastNode.nodeSize > 4 ||
78
+ lastContentNode.type.spec.content !== "inline*"
79
+ );
68
80
  },
69
81
  },
70
82
  }),