From 31bd475418e3bc731e38fb4a80bf222f8b212773 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Fri, 12 Dec 2025 14:45:04 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20paste=20content=20with?= =?UTF-8?q?=20comments=20from=20another=20document?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When pasting comments, the data-bn-thread-id attribute is present in the clipboard data. This indicates that the pasted content contains comments. But if the content with comments comes from another document, it will create orphaned comments that are not linked to this document and create errors. To avoid this, we refresh the threads to ensure that only comments relevant to the current document are displayed. --- CHANGELOG.md | 4 ++ .../app-impress/doc-comments.spec.ts | 43 +++++++++++++++++++ .../doc-editor/components/BlockNoteEditor.tsx | 20 +++++++++ .../doc-editor/components/comments/styles.tsx | 26 +++++++---- 4 files changed, 85 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a1dd073b5..b5e701ee64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to - 🚸(backend) use unaccented full name for user search #1637 - 🌐(backend) internationalize demo #1644 +### Fixed + +- 🐛(frontend) paste content with comments from another document #1732 + ## [4.1.0] - 2025-12-09 ### Added diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts index ce2490e4c5..347233e7fe 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts @@ -318,6 +318,49 @@ test.describe('Doc Comments', () => { await cleanup(); }); + + test('it checks comments pasting from another document', async ({ + page, + browserName, + }) => { + await createDoc(page, 'comment-doc-1', browserName, 1); + + // We add a comment in the first document + const editor1 = await writeInEditor({ page, text: 'Document One' }); + await editor1.getByText('Document One').selectText(); + await page.getByRole('button', { name: 'Comment' }).click(); + + const thread1 = page.locator('.bn-thread'); + await thread1.getByRole('paragraph').first().fill('Comment in Doc One'); + await thread1.locator('[data-test="save"]').click(); + await expect(thread1.getByText('Comment in Doc One').first()).toBeHidden(); + + await expect(editor1.getByText('Document One')).toHaveCSS( + 'background-color', + 'rgba(237, 180, 0, 0.4)', + ); + + await editor1.getByText('Document One').click(); + // We copy the content including the comment from the first document + await editor1.getByText('Document One').selectText(); + await page.keyboard.press('Control+C'); + + // We create a second document + await createDoc(page, 'comment-doc-2', browserName, 1); + + // We paste the content into the second document + const editor2 = await writeInEditor({ page, text: '' }); + await editor2.click(); + await page.keyboard.press('Control+V'); + + await expect(editor2.getByText('Document One')).toHaveCSS( + 'background-color', + 'rgba(0, 0, 0, 0)', + ); + + await editor2.getByText('Document One').click(); + await expect(page.locator('.bn-thread')).toBeHidden(); + }); }); test.describe('Doc Comments mobile', () => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index 83a3d913b2..6ec70151cd 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -162,6 +162,26 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { multi_column: multiColumnLocales?.[lang as keyof typeof multiColumnLocales], }, + pasteHandler: ({ event, defaultPasteHandler }) => { + // Get clipboard data + const blocknoteData = event.clipboardData?.getData('blocknote/html'); + + /** + * When pasting comments, the data-bn-thread-id + * attribute is present in the clipboard data. + * This indicates that the pasted content contains comments. + * But if the content with comments comes from another document, + * it will create orphaned comments that are not linked to this document + * and create errors. + * To avoid this, we refresh the threads to ensure that only comments + * relevant to the current document are displayed. + */ + if (blocknoteData && blocknoteData.includes('data-bn-thread-id')) { + void threadStore.refreshThreads(); + } + + return defaultPasteHandler(); + }, resolveUsers: async (userIds) => { return Promise.resolve( userIds.map((encodedURIUserId) => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx index e2a59a8c4d..d536dcaeb0 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx @@ -117,7 +117,7 @@ export const cssComments = ( } & svg { - color: var(--c--globals--colors--info-600); + color: var(--c--contextuals--background--semantic--brand--primary); } } @@ -134,15 +134,23 @@ export const cssComments = ( padding-inline: var(--c--globals--spacings--st); &[data-test='save'] { - border: 1px solid var(--c--globals--colors--info-600); - background: var(--c--globals--colors--info-600); - color: white; + border: 1px solid + var(--c--contextuals--background--semantic--brand--primary); + background: var( + --c--contextuals--background--semantic--brand--primary + ); + color: var( + --c--contextuals--content--semantic--brand--on-brand + ); } &[data-test='cancel'] { background: white; - border: 1px solid var(--c--globals--colors--gray-300); - color: var(--c--globals--colors--info-600); + border: 1px solid + var(--c--contextuals--border--surface--primary); + color: var( + --c--contextuals--background--semantic--brand--primary + ); } } } @@ -184,7 +192,9 @@ export const cssComments = ( button { font-size: 0; - background: var(--c--globals--colors--info-600); + background: var( + --c--contextuals--background--semantic--brand--primary + ); width: var(--c--globals--spacings--md); height: var(--c--globals--spacings--md); padding: var(--c--globals--spacings--0); @@ -197,7 +207,7 @@ export const cssComments = ( content: 'arrow_upward_alt'; font-family: 'Material Symbols Outlined Variable', sans-serif; font-size: 18px; - color: var(--c--globals--colors--gray-100); + color: var(--c--contextuals--content--semantic--brand--on-brand); } } }