diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a1dd073..b5e701ee 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 ce2490e4..347233e7 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 83a3d913..6ec70151 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 e2a59a8c..d536dcae 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); } } }