diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index d2eeaa0aa3..1166972fb0 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -1,4 +1,5 @@ import { InputRule, markInputRule } from "@tiptap/core"; +import type { MarkType } from "@tiptap/pm/model"; import Bold from "@tiptap/extension-bold"; import Code from "@tiptap/extension-code"; import Italic from "@tiptap/extension-italic"; @@ -139,6 +140,23 @@ export const defaultStyleSpecs = { strike: createStyleSpecFromTipTapMark(Strike, "boolean"), code: createStyleSpecFromTipTapMark( Code.extend({ + onBeforeCreate() { + // By default, code marks are configured to not overlap with any other + // marks with `exclude: "_"`. However, comment marks should still be + // allowed to overlap code marks. There is unfortunately no way to make + // the `exclude` option contain all possible marks except comments, so + // we instead remove the comment mark from it in `onBeforeCreate`. + const commentType = this.editor.schema.marks.comment; + const codeType = this.type as MarkType & { + excluded?: readonly MarkType[]; + }; + + if (commentType && codeType.excluded?.includes(commentType)) { + codeType.excluded = codeType.excluded.filter( + (markType) => markType !== commentType, + ); + } + }, addInputRules() { return [ // Matches any string that starts with a backtick, ends with a diff --git a/tests/src/end-to-end/comments/comments.test.tsx b/tests/src/end-to-end/comments/comments.test.tsx index 261040c093..a46c68e244 100644 --- a/tests/src/end-to-end/comments/comments.test.tsx +++ b/tests/src/end-to-end/comments/comments.test.tsx @@ -1,7 +1,7 @@ import App from "@examples/07-collaboration/09-comments-testing/src/App"; import { beforeEach, describe, expect, test, vi } from "vite-plus/test"; import { render } from "vitest-browser-react"; -import { browserName, page, userEvent } from "../../utils/context.js"; +import { browserName, MOD, page, userEvent } from "../../utils/context.js"; import { EDITOR_SELECTOR, LINK_BUTTON_SELECTOR } from "../../utils/const.js"; import { expectElement, @@ -133,6 +133,36 @@ describe("Check Comments functionality", () => { ).toBeVisible(); }); + test("Should preserve existing comments when adding a code mark", async () => { + await focusOnEditor(); + + await userEvent.keyboard("hello"); + await doubleClickElement(page.getByText("hello").element()); + + await userEvent.click(await waitForSelector('[data-test="addcomment"]')); + await waitForSelector(".bn-thread"); + + await userEvent.keyboard("test comment"); + await userEvent.click(await waitForSelector('button[data-test="save"]')); + + // Re-select the commented text and toggle inline code on it (Cmd/Ctrl+E). + await doubleClickElement( + document.querySelectorAll("span.bn-thread-mark")[0] as HTMLElement, + ); + await userEvent.keyboard(`{${MOD}>}e{/${MOD}}`); + + // The comment must be preserved, and the text must now also be inline code, + // i.e. the comment and code marks coexist on the same text. + await expectElement( + await waitForSelector("span.bn-thread-mark"), + ).toBeVisible(); + await expectElement( + await waitForSelector( + "span.bn-thread-mark code, code span.bn-thread-mark", + ), + ).toBeVisible(); + }); + test.skipIf(browserName === "webkit")( "Should select thread on first click and open link on second click", async () => {