@blocknote/core 0.32.0 → 0.34.0

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 (43) hide show
  1. package/dist/blocknote.cjs +8 -8
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +1816 -1665
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/tsconfig.tsbuildinfo +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +2 -2
  8. package/src/api/__snapshots__/blocks-indented-changed.json +129 -0
  9. package/src/api/__snapshots__/blocks-moved-deeper-into-nesting.json +164 -0
  10. package/src/api/__snapshots__/blocks-moved-multiple-in-same-transaction.json +188 -0
  11. package/src/api/__snapshots__/blocks-moved-to-different-parent.json +78 -0
  12. package/src/api/__snapshots__/blocks-moved-to-root-level.json +78 -0
  13. package/src/api/__snapshots__/blocks-outdented-changed.json +129 -0
  14. package/src/api/blockManipulation/commands/nestBlock/nestBlock.ts +58 -59
  15. package/src/api/nodeUtil.test.ts +228 -1
  16. package/src/api/nodeUtil.ts +135 -118
  17. package/src/api/parsers/markdown/detectMarkdown.test.ts +211 -0
  18. package/src/api/parsers/markdown/detectMarkdown.ts +3 -2
  19. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +2 -1
  20. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +2 -1
  21. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +2 -0
  22. package/src/blocks/defaultBlockTypeGuards.ts +30 -0
  23. package/src/editor/BlockNoteEditor.ts +27 -10
  24. package/src/editor/BlockNoteExtensions.ts +3 -5
  25. package/src/exporter/Exporter.ts +2 -0
  26. package/src/exporter/mapping.ts +1 -0
  27. package/src/extensions/BlockChange/BlockChangePlugin.ts +66 -0
  28. package/src/extensions/Collaboration/CursorPlugin.ts +33 -2
  29. package/src/extensions/SideMenu/SideMenuPlugin.ts +290 -207
  30. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +22 -12
  31. package/src/schema/inlineContent/types.ts +8 -0
  32. package/src/util/browser.ts +11 -1
  33. package/types/src/api/nodeUtil.d.ts +15 -21
  34. package/types/src/api/parsers/markdown/detectMarkdown.test.d.ts +1 -0
  35. package/types/src/blocks/defaultBlockTypeGuards.d.ts +7 -1
  36. package/types/src/editor/BlockNoteEditor.d.ts +11 -8
  37. package/types/src/editor/BlockNoteExtensions.d.ts +0 -1
  38. package/types/src/exporter/Exporter.d.ts +1 -1
  39. package/types/src/exporter/mapping.d.ts +1 -1
  40. package/types/src/extensions/BlockChange/BlockChangePlugin.d.ts +15 -0
  41. package/types/src/extensions/Collaboration/CursorPlugin.d.ts +6 -0
  42. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +50 -9
  43. package/types/src/schema/inlineContent/types.d.ts +4 -0
@@ -120,6 +120,7 @@ import { EventEmitter } from "../util/EventEmitter.js";
120
120
  import { BlockNoteExtension } from "./BlockNoteExtension.js";
121
121
 
122
122
  import "../style.css";
123
+ import { BlockChangePlugin } from "../extensions/BlockChange/BlockChangePlugin.js";
123
124
 
124
125
  /**
125
126
  * A factory function that returns a BlockNoteExtension
@@ -312,15 +313,6 @@ export type BlockNoteEditorOptions<
312
313
  */
313
314
  setIdAttribute?: boolean;
314
315
 
315
- /**
316
- * The detection mode for showing the side menu - "viewport" always shows the
317
- * side menu for the block next to the mouse cursor, while "editor" only shows
318
- * it when hovering the editor or the side menu itself.
319
- *
320
- * @default "viewport"
321
- */
322
- sideMenuDetection: "viewport" | "editor";
323
-
324
316
  /**
325
317
  Select desired behavior when pressing `Tab` (or `Shift-Tab`). Specifically,
326
318
  what should happen when a user has selected multiple blocks while a toolbar
@@ -639,7 +631,6 @@ export class BlockNoteEditor<
639
631
  dropCursor: this.options.dropCursor ?? dropCursor,
640
632
  placeholders: newOptions.placeholders,
641
633
  tabBehavior: newOptions.tabBehavior,
642
- sideMenuDetection: newOptions.sideMenuDetection || "viewport",
643
634
  comments: newOptions.comments,
644
635
  pasteHandler: newOptions.pasteHandler,
645
636
  });
@@ -1606,6 +1597,32 @@ export class BlockNoteEditor<
1606
1597
  (this.extensions["yCursorPlugin"] as CursorPlugin).updateUser(user);
1607
1598
  }
1608
1599
 
1600
+ /**
1601
+ * Registers a callback which will be called before any change is applied to the editor, allowing you to cancel the change.
1602
+ */
1603
+ public beforeChange(
1604
+ /**
1605
+ * If the callback returns `false`, the change will be canceled & not applied to the editor.
1606
+ */
1607
+ callback: (
1608
+ editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
1609
+ context: {
1610
+ getChanges: () => BlocksChanged<BSchema, ISchema, SSchema>;
1611
+ tr: Transaction;
1612
+ },
1613
+ ) => boolean | void,
1614
+ ): () => void {
1615
+ if (this.headless) {
1616
+ return () => {
1617
+ // noop
1618
+ };
1619
+ }
1620
+
1621
+ return (this.extensions["blockChange"] as BlockChangePlugin).subscribe(
1622
+ (context) => callback(this, context),
1623
+ );
1624
+ }
1625
+
1609
1626
  /**
1610
1627
  * A callback function that runs whenever the editor's contents change.
1611
1628
  *
@@ -11,6 +11,7 @@ import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboar
11
11
  import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
12
12
  import type { ThreadStore } from "../comments/index.js";
13
13
  import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
14
+ import { BlockChangePlugin } from "../extensions/BlockChange/BlockChangePlugin.js";
14
15
  import { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
15
16
  import { SyncPlugin } from "../extensions/Collaboration/SyncPlugin.js";
16
17
  import { UndoPlugin } from "../extensions/Collaboration/UndoPlugin.js";
@@ -90,7 +91,6 @@ type ExtensionOptions<
90
91
  string | undefined
91
92
  >;
92
93
  tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
93
- sideMenuDetection: "viewport" | "editor";
94
94
  comments?: {
95
95
  threadStore: ThreadStore;
96
96
  };
@@ -133,10 +133,7 @@ export const getBlockNoteExtensions = <
133
133
  opts.editor,
134
134
  );
135
135
  ret["linkToolbar"] = new LinkToolbarProsemirrorPlugin(opts.editor);
136
- ret["sideMenu"] = new SideMenuProsemirrorPlugin(
137
- opts.editor,
138
- opts.sideMenuDetection,
139
- );
136
+ ret["sideMenu"] = new SideMenuProsemirrorPlugin(opts.editor);
140
137
  ret["suggestionMenus"] = new SuggestionMenuProseMirrorPlugin(opts.editor);
141
138
  ret["filePanel"] = new FilePanelProsemirrorPlugin(opts.editor as any);
142
139
  ret["placeholder"] = new PlaceholderPlugin(opts.editor, opts.placeholders);
@@ -150,6 +147,7 @@ export const getBlockNoteExtensions = <
150
147
  }
151
148
 
152
149
  ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin();
150
+ ret["blockChange"] = new BlockChangePlugin();
153
151
 
154
152
  ret["showSelection"] = new ShowSelectionPlugin(opts.editor);
155
153
 
@@ -90,12 +90,14 @@ export abstract class Exporter<
90
90
  block: BlockFromConfig<B[keyof B], I, S>,
91
91
  nestingLevel: number,
92
92
  numberedListIndex: number,
93
+ children?: Array<Awaited<RB>>,
93
94
  ) {
94
95
  return this.mappings.blockMapping[block.type](
95
96
  block,
96
97
  this,
97
98
  nestingLevel,
98
99
  numberedListIndex,
100
+ children,
99
101
  );
100
102
  }
101
103
  }
@@ -27,6 +27,7 @@ export type BlockMapping<
27
27
  exporter: Exporter<any, any, any, RB, RI, any, any>,
28
28
  nestingLevel: number,
29
29
  numberedListIndex?: number,
30
+ children?: Array<Awaited<RB>>,
30
31
  ) => RB | Promise<RB>;
31
32
  };
32
33
 
@@ -0,0 +1,66 @@
1
+ import { Plugin, Transaction } from "prosemirror-state";
2
+ import { getBlocksChangedByTransaction } from "../../api/nodeUtil.js";
3
+ import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
4
+ import { BlocksChanged } from "../../index.js";
5
+
6
+ /**
7
+ * This plugin can filter transactions before they are applied to the editor, but with a higher-level API than `filterTransaction` from prosemirror.
8
+ */
9
+ export class BlockChangePlugin extends BlockNoteExtension {
10
+ public static key() {
11
+ return "blockChange";
12
+ }
13
+
14
+ private beforeChangeCallbacks: ((context: {
15
+ getChanges: () => BlocksChanged<any, any, any>;
16
+ tr: Transaction;
17
+ }) => boolean | void)[] = [];
18
+
19
+ constructor() {
20
+ super();
21
+
22
+ this.addProsemirrorPlugin(
23
+ new Plugin({
24
+ filterTransaction: (tr) => {
25
+ let changes:
26
+ | ReturnType<typeof getBlocksChangedByTransaction>
27
+ | undefined = undefined;
28
+
29
+ return this.beforeChangeCallbacks.reduce((acc, cb) => {
30
+ if (acc === false) {
31
+ // We only care that we hit a `false` result, so we can stop iterating.
32
+ return acc;
33
+ }
34
+ return (
35
+ cb({
36
+ getChanges() {
37
+ if (changes) {
38
+ return changes;
39
+ }
40
+ changes = getBlocksChangedByTransaction(tr);
41
+ return changes;
42
+ },
43
+ tr,
44
+ }) !== false
45
+ );
46
+ }, true);
47
+ },
48
+ }),
49
+ );
50
+ }
51
+
52
+ public subscribe(
53
+ callback: (context: {
54
+ getChanges: () => BlocksChanged<any, any, any>;
55
+ tr: Transaction;
56
+ }) => boolean | void,
57
+ ) {
58
+ this.beforeChangeCallbacks.push(callback);
59
+
60
+ return () => {
61
+ this.beforeChangeCallbacks = this.beforeChangeCallbacks.filter(
62
+ (cb) => cb !== callback,
63
+ );
64
+ };
65
+ }
66
+ }
@@ -131,6 +131,27 @@ export class CursorPlugin extends BlockNoteExtension {
131
131
  this.provider.awareness.setLocalStateField("user", user);
132
132
  };
133
133
 
134
+ /**
135
+ * Determine whether the foreground color should be white or black based on a provided background color
136
+ * Inspired by: https://stackoverflow.com/a/3943023
137
+ *
138
+ */
139
+ public static isDarkColor(bgColor: string): boolean {
140
+ const color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor;
141
+ const r = parseInt(color.substring(0, 2), 16); // hexToR
142
+ const g = parseInt(color.substring(2, 4), 16); // hexToG
143
+ const b = parseInt(color.substring(4, 6), 16); // hexToB
144
+ const uicolors = [r / 255, g / 255, b / 255];
145
+ const c = uicolors.map((col) => {
146
+ if (col <= 0.03928) {
147
+ return col / 12.92;
148
+ }
149
+ return Math.pow((col + 0.055) / 1.055, 2.4);
150
+ });
151
+ const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
152
+ return L <= 0.179;
153
+ }
154
+
134
155
  public static defaultCursorRender = (user: CollaborationUser) => {
135
156
  const cursorElement = document.createElement("span");
136
157
 
@@ -139,12 +160,22 @@ export class CursorPlugin extends BlockNoteExtension {
139
160
  const caretElement = document.createElement("span");
140
161
  caretElement.setAttribute("contentedEditable", "false");
141
162
  caretElement.classList.add("bn-collaboration-cursor__caret");
142
- caretElement.setAttribute("style", `background-color: ${user.color}`);
163
+ caretElement.setAttribute(
164
+ "style",
165
+ `background-color: ${user.color}; color: ${
166
+ CursorPlugin.isDarkColor(user.color) ? "white" : "black"
167
+ }`,
168
+ );
143
169
 
144
170
  const labelElement = document.createElement("span");
145
171
 
146
172
  labelElement.classList.add("bn-collaboration-cursor__label");
147
- labelElement.setAttribute("style", `background-color: ${user.color}`);
173
+ labelElement.setAttribute(
174
+ "style",
175
+ `background-color: ${user.color}; color: ${
176
+ CursorPlugin.isDarkColor(user.color) ? "white" : "black"
177
+ }`,
178
+ );
148
179
  labelElement.insertBefore(document.createTextNode(user.name), null);
149
180
 
150
181
  caretElement.insertBefore(labelElement, null);