Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import { DefaultSuggestionItem } from "./DefaultSuggestionItem.js";
import { SuggestionMenu } from "./SuggestionMenu.js";

// Sets the editor's text cursor position to the next content editable block,
// so either a block with inline content or a table. The last block is always a
// paragraph, so this function won't try to set the cursor position past the
// last block.
// so either a block with inline content or a table. If no such block exists
// after the current one, an empty paragraph is appended to the end of the
// document and the cursor is moved to it.
function setSelectionToNextContentEditableBlock<
BSchema extends BlockSchema,
I extends InlineContentSchema,
Expand All @@ -29,6 +29,16 @@ function setSelectionToNextContentEditableBlock<
while (contentType === "none") {
block = editor.getTextCursorPosition().nextBlock;
if (block === undefined) {
// No content editable block exists after the current one, so we append
// an empty paragraph to the end of the document and move the cursor to
// it.
const lastBlock = editor.document[editor.document.length - 1];
const newBlock = editor.insertBlocks(
[{ type: "paragraph" }],
lastBlock,
"after",
Comment on lines +35 to +39
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid hard-coding "paragraph" in a schema-generic cursor fallback.

Line 37 always inserts { type: "paragraph" }, but this helper is reached through exported insertOrUpdateBlockForSlashMenu. In custom schemas without paragraph, this path will fail at runtime when insertion reaches document end. Pick an available inline-content type from editor.schema.blockSchema (or fail with a clear schema error) instead of assuming paragraph.

Proposed direction
-      const newBlock = editor.insertBlocks(
-        [{ type: "paragraph" }],
+      const fallbackInlineType = Object.entries(editor.schema.blockSchema).find(
+        ([, spec]) => spec.content === "inline",
+      )?.[0];
+      if (!fallbackInlineType) {
+        throw new Error(
+          "No inline-content block type available to place text cursor.",
+        );
+      }
+      const newBlock = editor.insertBlocks(
+        [{ type: fallbackInlineType as keyof BSchema }],
         lastBlock,
         "after",
       )[0];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts`
around lines 35 - 39, The code currently hard-codes inserting { type:
"paragraph" } in getDefaultSlashMenuItems (used by
insertOrUpdateBlockForSlashMenu), which will break for custom schemas without a
paragraph type; update the fallback to select a valid inline-content block type
from editor.schema.blockSchema (e.g., find the first block whose content model
allows inline content) and use that type in the call to editor.insertBlocks, and
if none exists, throw a clear schema error mentioning
insertOrUpdateBlockForSlashMenu and editor.schema.blockSchema so failure is
explicit.

)[0];
editor.setTextCursorPosition(newBlock, "end");
return;
}
contentType = editor.schema.blockSchema[block.type].content as
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions tests/src/end-to-end/dragdrop/dragdrop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ describe("Check Block Dragging Functionality", () => {
await focusOnEditor();
await executeSlashCommand("image");
await userEvent.keyboard("{Escape}");
await userEvent.click(await waitForSelector(".bn-trailing-block"));
const paragraphs = document.querySelectorAll(PARAGRAPH_SELECTOR);
await userEvent.click(paragraphs[paragraphs.length - 1]);
await insertHeading(1);

await dragAndDropBlock(IMAGE_SELECTOR, H_ONE_BLOCK_SELECTOR, false);
Expand All @@ -119,7 +120,8 @@ describe("Check Block Dragging Functionality", () => {
await focusOnEditor();
await executeSlashCommand("image");
await userEvent.keyboard("{Escape}");
await userEvent.click(await waitForSelector(".bn-trailing-block"));
const paragraphs = document.querySelectorAll(PARAGRAPH_SELECTOR);
await userEvent.click(paragraphs[paragraphs.length - 1]);
await insertHeading(1);

await dragAndDropBlock(IMAGE_SELECTOR, H_ONE_BLOCK_SELECTOR, false);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions tests/src/end-to-end/images/__snapshots__/createImage.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@
}
}
]
},
{
"type": "blockContainer",
"attrs": {
"id": "1"
},
"content": [
{
"type": "paragraph",
"attrs": {
"backgroundColor": "default",
"textColor": "default",
"textAlignment": "left"
}
}
]
}
]
}
Expand Down
16 changes: 16 additions & 0 deletions tests/src/end-to-end/images/__snapshots__/embedImage.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@
}
}
]
},
{
"type": "blockContainer",
"attrs": {
"id": "1"
},
"content": [
{
"type": "paragraph",
"attrs": {
"backgroundColor": "default",
"textColor": "default",
"textAlignment": "left"
}
}
]
}
]
}
Expand Down
16 changes: 16 additions & 0 deletions tests/src/end-to-end/images/__snapshots__/resizeImage.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@
}
}
]
},
{
"type": "blockContainer",
"attrs": {
"id": "1"
},
"content": [
{
"type": "paragraph",
"attrs": {
"backgroundColor": "default",
"textColor": "default",
"textAlignment": "left"
}
}
]
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@
]
}
]
},
{
"type": "blockContainer",
"attrs": {
"id": "2"
},
"content": [
{
"type": "paragraph",
"attrs": {
"backgroundColor": "default",
"textColor": "default",
"textAlignment": "left"
}
}
]
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@
]
}
]
},
{
"type": "blockContainer",
"attrs": {
"id": "2"
},
"content": [
{
"type": "paragraph",
"attrs": {
"backgroundColor": "default",
"textColor": "default",
"textAlignment": "left"
}
}
]
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ describe("Check Keyboard Handlers' Behaviour", () => {
await executeSlashCommand("image");
await userEvent.keyboard("{Escape}"); // Close file panel

await userEvent.keyboard("{ArrowUp}");
await userEvent.keyboard("{Delete}");

await compareDocToSnapshot("deleteSelectedImage");
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions tests/src/utils/copypaste.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { MOD, userEvent } from "./context.js";
import { waitForSelector } from "./editor.js";

export function selectAll() {
return userEvent.keyboard(`{${MOD}>}a{/${MOD}}`);
Expand All @@ -9,7 +8,18 @@ export async function copyPaste() {
await userEvent.keyboard(`{${MOD}>}c{/${MOD}}`);
// Exit out of any menus/toolbars which may block the trailing block.
await userEvent.keyboard("{Escape}");
await userEvent.click(await waitForSelector(".bn-trailing-block"));
// The trailing block isn't always present (e.g. when the editor's last block
// can't have one), so fall back to the last paragraph.
const trailingBlock =
document.querySelector<HTMLElement>(".bn-trailing-block");
if (trailingBlock) {
await userEvent.click(trailingBlock);
} else {
const paragraphs = document.querySelectorAll<HTMLElement>(
'[data-content-type="paragraph"]',
);
await userEvent.click(paragraphs[paragraphs.length - 1]);
Comment on lines +18 to +21
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard the paragraph fallback before clicking.

If .bn-trailing-block is absent and no paragraph exists, Line 21 clicks undefined and fails non-deterministically. Add an explicit empty-check with a clear error.

Suggested fix
   } else {
     const paragraphs = document.querySelectorAll<HTMLElement>(
       '[data-content-type="paragraph"]',
     );
+    if (paragraphs.length === 0) {
+      throw new Error(
+        "copyPaste: no trailing block or paragraph available to focus before paste.",
+      );
+    }
     await userEvent.click(paragraphs[paragraphs.length - 1]);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const paragraphs = document.querySelectorAll<HTMLElement>(
'[data-content-type="paragraph"]',
);
await userEvent.click(paragraphs[paragraphs.length - 1]);
const paragraphs = document.querySelectorAll<HTMLElement>(
'[data-content-type="paragraph"]',
);
if (paragraphs.length === 0) {
throw new Error(
"copyPaste: no trailing block or paragraph available to focus before paste.",
);
}
await userEvent.click(paragraphs[paragraphs.length - 1]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/utils/copypaste.ts` around lines 18 - 21, Guard the paragraph
fallback by checking that the NodeList referenced by paragraphs has at least one
element before calling userEvent.click; if paragraphs.length === 0 (and
.bn-trailing-block is absent) throw a clear error or fail the test instead of
calling userEvent.click(undefined). Update the code around the paragraphs
variable and the userEvent.click(paragraphs[paragraphs.length - 1]) call to
perform this explicit empty-check and error message.

}
await userEvent.keyboard(`{${MOD}>}v{/${MOD}}`);
}

Expand Down
Loading