From 27ad370c49e764182ea7785a81bf5b100c87589a Mon Sep 17 00:00:00 2001
From: Matthew Lipski <matthewlipski@gmail.com>
Date: Fri, 21 Mar 2025 17:20:46 +0100
Subject: [PATCH 1/5] Replaced TipTap hard break extension with our own

---
 .../core/src/editor/BlockNoteExtensions.ts    |  4 +-
 .../src/extensions/HardBreak/HardBreak.ts     | 84 +++++++++++++++++++
 .../KeyboardShortcutsExtension.ts             |  1 +
 3 files changed, 87 insertions(+), 2 deletions(-)
 create mode 100644 packages/core/src/extensions/HardBreak/HardBreak.ts

diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts
index 9d7f1fd0fc..a5233f11f6 100644
--- a/packages/core/src/editor/BlockNoteExtensions.ts
+++ b/packages/core/src/editor/BlockNoteExtensions.ts
@@ -1,6 +1,5 @@
 import { AnyExtension, Extension, extensions } from "@tiptap/core";
 import { Gapcursor } from "@tiptap/extension-gapcursor";
-import { HardBreak } from "@tiptap/extension-hard-break";
 import { History } from "@tiptap/extension-history";
 import { Link } from "@tiptap/extension-link";
 import { Text } from "@tiptap/extension-text";
@@ -17,6 +16,7 @@ import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js";
 import type { ThreadStore } from "../comments/index.js";
 import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
 import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
+import { HardBreak } from "../extensions/HardBreak/HardBreak.js";
 import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js";
 import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
 import {
@@ -179,7 +179,7 @@ const getTipTapExtensions = <
       types: ["blockContainer", "columnList", "column"],
       setIdAttribute: opts.setIdAttribute,
     }),
-    HardBreak.extend({ priority: 10 }),
+    HardBreak,
     // Comments,
 
     // basics:
diff --git a/packages/core/src/extensions/HardBreak/HardBreak.ts b/packages/core/src/extensions/HardBreak/HardBreak.ts
new file mode 100644
index 0000000000..54b1d3e3f8
--- /dev/null
+++ b/packages/core/src/extensions/HardBreak/HardBreak.ts
@@ -0,0 +1,84 @@
+// Stripped down version of the TipTap HardBreak extension:
+// https://github.com/ueberdosis/tiptap/blob/f3258d9ee5fb7979102fe63434f6ea4120507311/packages/extension-hard-break/src/hard-break.ts#L80
+// Changes:
+// - Removed options
+// - Removed keyboard shortcuts & moved them to the `KeyboardShortcutsExtension`
+// - Removed `setHardBreak` check if parent node is isolating (this is beacuse
+// custom blocks are isolating).
+// - Added priority
+import { mergeAttributes, Node } from "@tiptap/core";
+
+declare module "@tiptap/core" {
+  interface Commands<ReturnType> {
+    hardBreak: {
+      /**
+       * Add a hard break
+       * @example editor.commands.setHardBreak()
+       */
+      setHardBreak: () => ReturnType;
+    };
+  }
+}
+
+export const HardBreak = Node.create({
+  name: "hardBreak",
+
+  inline: true,
+
+  group: "inline",
+
+  selectable: false,
+
+  linebreakReplacement: true,
+
+  priority: 10,
+
+  parseHTML() {
+    return [{ tag: "br" }];
+  },
+
+  renderHTML({ HTMLAttributes }) {
+    return ["br", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
+  },
+
+  renderText() {
+    return "\n";
+  },
+
+  addCommands() {
+    return {
+      setHardBreak:
+        () =>
+        ({ commands, chain, state, editor }) => {
+          return commands.first([
+            () => commands.exitCode(),
+            () =>
+              commands.command(() => {
+                const { selection, storedMarks } = state;
+
+                const { keepMarks } = this.options;
+                const { splittableMarks } = editor.extensionManager;
+                const marks =
+                  storedMarks ||
+                  (selection.$to.parentOffset && selection.$from.marks());
+
+                return chain()
+                  .insertContent({ type: this.name })
+                  .command(({ tr, dispatch }) => {
+                    if (dispatch && marks && keepMarks) {
+                      const filteredMarks = marks.filter((mark) =>
+                        splittableMarks.includes(mark.type.name)
+                      );
+
+                      tr.ensureMarks(filteredMarks);
+                    }
+
+                    return true;
+                  })
+                  .run();
+              }),
+          ]);
+        },
+    };
+  },
+});
diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
index ae00f7074a..dd70ee55dd 100644
--- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
+++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
@@ -543,6 +543,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
       Backspace: handleBackspace,
       Delete: handleDelete,
       Enter: handleEnter,
+      "Shift-Enter": () => this.editor.commands.setHardBreak(),
       // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
       // editor since the browser will try to use tab for keyboard navigation.
       Tab: () => {

From 13b90073862d9e6ba753adc15a2521d5e89c1108 Mon Sep 17 00:00:00 2001
From: Matthew Lipski <matthewlipski@gmail.com>
Date: Tue, 25 Mar 2025 19:05:44 +0100
Subject: [PATCH 2/5] Removed `setHardBreak` command and dependency

---
 package-lock.json                             | 13 -----
 packages/core/package.json                    |  1 -
 .../TableBlockContent/TableExtension.ts       |  2 +-
 .../src/extensions/HardBreak/HardBreak.ts     | 53 +------------------
 .../KeyboardShortcutsExtension.ts             |  3 +-
 5 files changed, 5 insertions(+), 67 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 87692758d1..c6e9fd50bc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10622,18 +10622,6 @@
         "@tiptap/pm": "^2.7.0"
       }
     },
-    "node_modules/@tiptap/extension-hard-break": {
-      "version": "2.11.5",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.5.tgz",
-      "integrity": "sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
     "node_modules/@tiptap/extension-history": {
       "version": "2.11.5",
       "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.5.tgz",
@@ -31224,7 +31212,6 @@
         "@tiptap/extension-collaboration": "^2.11.5",
         "@tiptap/extension-collaboration-cursor": "^2.11.5",
         "@tiptap/extension-gapcursor": "^2.11.5",
-        "@tiptap/extension-hard-break": "^2.11.5",
         "@tiptap/extension-history": "^2.11.5",
         "@tiptap/extension-horizontal-rule": "^2.11.5",
         "@tiptap/extension-italic": "^2.11.5",
diff --git a/packages/core/package.json b/packages/core/package.json
index f99e435d17..07183e8c2d 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -69,7 +69,6 @@
     "@tiptap/extension-collaboration": "^2.11.5",
     "@tiptap/extension-collaboration-cursor": "^2.11.5",
     "@tiptap/extension-gapcursor": "^2.11.5",
-    "@tiptap/extension-hard-break": "^2.11.5",
     "@tiptap/extension-history": "^2.11.5",
     "@tiptap/extension-horizontal-rule": "^2.11.5",
     "@tiptap/extension-italic": "^2.11.5",
diff --git a/packages/core/src/blocks/TableBlockContent/TableExtension.ts b/packages/core/src/blocks/TableBlockContent/TableExtension.ts
index 33b47c0c90..0ed6836eea 100644
--- a/packages/core/src/blocks/TableBlockContent/TableExtension.ts
+++ b/packages/core/src/blocks/TableBlockContent/TableExtension.ts
@@ -31,7 +31,7 @@ export const TableExtension = Extension.create({
           this.editor.state.selection.$head.parent.type.name ===
             "tableParagraph"
         ) {
-          this.editor.commands.setHardBreak();
+          this.editor.commands.insertContent({ type: "hardBreak" });
 
           return true;
         }
diff --git a/packages/core/src/extensions/HardBreak/HardBreak.ts b/packages/core/src/extensions/HardBreak/HardBreak.ts
index 54b1d3e3f8..6c1a99b241 100644
--- a/packages/core/src/extensions/HardBreak/HardBreak.ts
+++ b/packages/core/src/extensions/HardBreak/HardBreak.ts
@@ -3,23 +3,11 @@
 // Changes:
 // - Removed options
 // - Removed keyboard shortcuts & moved them to the `KeyboardShortcutsExtension`
-// - Removed `setHardBreak` check if parent node is isolating (this is beacuse
-// custom blocks are isolating).
+// - Removed `setHardBreak` command (added a simpler version in the Shift+Enter
+// handler in `KeyboardShortcutsExtension`).
 // - Added priority
 import { mergeAttributes, Node } from "@tiptap/core";
 
-declare module "@tiptap/core" {
-  interface Commands<ReturnType> {
-    hardBreak: {
-      /**
-       * Add a hard break
-       * @example editor.commands.setHardBreak()
-       */
-      setHardBreak: () => ReturnType;
-    };
-  }
-}
-
 export const HardBreak = Node.create({
   name: "hardBreak",
 
@@ -44,41 +32,4 @@ export const HardBreak = Node.create({
   renderText() {
     return "\n";
   },
-
-  addCommands() {
-    return {
-      setHardBreak:
-        () =>
-        ({ commands, chain, state, editor }) => {
-          return commands.first([
-            () => commands.exitCode(),
-            () =>
-              commands.command(() => {
-                const { selection, storedMarks } = state;
-
-                const { keepMarks } = this.options;
-                const { splittableMarks } = editor.extensionManager;
-                const marks =
-                  storedMarks ||
-                  (selection.$to.parentOffset && selection.$from.marks());
-
-                return chain()
-                  .insertContent({ type: this.name })
-                  .command(({ tr, dispatch }) => {
-                    if (dispatch && marks && keepMarks) {
-                      const filteredMarks = marks.filter((mark) =>
-                        splittableMarks.includes(mark.type.name)
-                      );
-
-                      tr.ensureMarks(filteredMarks);
-                    }
-
-                    return true;
-                  })
-                  .run();
-              }),
-          ]);
-        },
-    };
-  },
 });
diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
index dd70ee55dd..148a7e2579 100644
--- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
+++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
@@ -543,7 +543,8 @@ export const KeyboardShortcutsExtension = Extension.create<{
       Backspace: handleBackspace,
       Delete: handleDelete,
       Enter: handleEnter,
-      "Shift-Enter": () => this.editor.commands.setHardBreak(),
+      "Shift-Enter": () =>
+        this.editor.commands.insertContent({ type: "hardBreak" }),
       // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
       // editor since the browser will try to use tab for keyboard navigation.
       Tab: () => {

From 4246e654f0689c0b37fcf42beab40ba254fd384e Mon Sep 17 00:00:00 2001
From: Matthew Lipski <matthewlipski@gmail.com>
Date: Wed, 26 Mar 2025 17:06:02 +0100
Subject: [PATCH 3/5] Added hard break shortcut configuration to API

---
 .../KeyboardShortcutsExtension.ts             | 38 +++++++++++++++++--
 packages/core/src/schema/blocks/types.ts      |  1 +
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
index 148a7e2579..4a651e90b4 100644
--- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
+++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
@@ -437,8 +437,8 @@ export const KeyboardShortcutsExtension = Extension.create<{
           }),
       ]);
 
-    const handleEnter = () =>
-      this.editor.commands.first(({ commands }) => [
+    const handleEnter = (withShift = false) => {
+      return this.editor.commands.first(({ commands }) => [
         // Removes a level of nesting if the block is empty & indented, while the selection is also empty & at the start
         // of the block.
         () =>
@@ -467,6 +467,36 @@ export const KeyboardShortcutsExtension = Extension.create<{
               return commands.liftListItem("blockContainer");
             }
 
+            return false;
+          }),
+        // Creates a hard break if block is configured to do so.
+        () =>
+          commands.command(({ state }) => {
+            const blockInfo = getBlockInfoFromSelection(state);
+
+            const blockHardBreakShortcut =
+              this.options.editor.schema.blockSchema[blockInfo.blockNoteType]
+                .hardBreakShortcut;
+
+            if (blockHardBreakShortcut === "none") {
+              return false;
+            }
+
+            if (
+              // If shortcut is not configured, or is configured as "shift+enter",
+              // create a hard break for shift+enter, but not for enter.
+              ((blockHardBreakShortcut === undefined ||
+                blockHardBreakShortcut === "shift+enter") &&
+                withShift) ||
+              // If shortcut is configured as "enter", create a hard break for
+              // both enter and shift+enter.
+              blockHardBreakShortcut === "enter"
+            ) {
+              return commands.insertContent({
+                type: "hardBreak",
+              });
+            }
+
             return false;
           }),
         // Creates a new block and moves the selection to it if the current one is empty, while the selection is also
@@ -538,11 +568,13 @@ export const KeyboardShortcutsExtension = Extension.create<{
             return false;
           }),
       ]);
+    };
 
     return {
       Backspace: handleBackspace,
       Delete: handleDelete,
-      Enter: handleEnter,
+      Enter: () => handleEnter(),
+      "Shift-Enter": () => handleEnter(true),
       "Shift-Enter": () =>
         this.editor.commands.insertContent({ type: "hardBreak" }),
       // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts
index dac8f5c9e9..0db756ec74 100644
--- a/packages/core/src/schema/blocks/types.ts
+++ b/packages/core/src/schema/blocks/types.ts
@@ -63,6 +63,7 @@ export type BlockConfig =
       content: "inline" | "none" | "table";
       isSelectable?: boolean;
       isFileBlock?: false;
+      hardBreakShortcut?: "shift+enter" | "enter" | "none";
     }
   | FileBlockConfig;
 

From 68882d58b7676aae7ad2f4feac40a156c8cf616e Mon Sep 17 00:00:00 2001
From: Matthew Lipski <matthewlipski@gmail.com>
Date: Thu, 27 Mar 2025 10:54:56 +0100
Subject: [PATCH 4/5] Fixed lint

---
 .../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts  | 2 --
 1 file changed, 2 deletions(-)

diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
index 4a651e90b4..cdc27e94ef 100644
--- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
+++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
@@ -575,8 +575,6 @@ export const KeyboardShortcutsExtension = Extension.create<{
       Delete: handleDelete,
       Enter: () => handleEnter(),
       "Shift-Enter": () => handleEnter(true),
-      "Shift-Enter": () =>
-        this.editor.commands.insertContent({ type: "hardBreak" }),
       // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
       // editor since the browser will try to use tab for keyboard navigation.
       Tab: () => {

From 98716a10297f3b08146285582f1297c4e281d9ee Mon Sep 17 00:00:00 2001
From: Matthew Lipski <matthewlipski@gmail.com>
Date: Thu, 27 Mar 2025 15:09:30 +0100
Subject: [PATCH 5/5] Small cleanup

---
 .../KeyboardShortcuts/KeyboardShortcutsExtension.ts       | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
index cdc27e94ef..9e7d540f64 100644
--- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
+++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
@@ -474,9 +474,9 @@ export const KeyboardShortcutsExtension = Extension.create<{
           commands.command(({ state }) => {
             const blockInfo = getBlockInfoFromSelection(state);
 
-            const blockHardBreakShortcut =
+            const blockHardBreakShortcut: "shift+enter" | "enter" | "none" =
               this.options.editor.schema.blockSchema[blockInfo.blockNoteType]
-                .hardBreakShortcut;
+                .hardBreakShortcut ?? "shift+enter";
 
             if (blockHardBreakShortcut === "none") {
               return false;
@@ -485,9 +485,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
             if (
               // If shortcut is not configured, or is configured as "shift+enter",
               // create a hard break for shift+enter, but not for enter.
-              ((blockHardBreakShortcut === undefined ||
-                blockHardBreakShortcut === "shift+enter") &&
-                withShift) ||
+              (blockHardBreakShortcut === "shift+enter" && withShift) ||
               // If shortcut is configured as "enter", create a hard break for
               // both enter and shift+enter.
               blockHardBreakShortcut === "enter"