diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3b5e6f02..f610990f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,17 @@ and this project adheres to ## [Unreleased] +### Added + +- ✨ Add comments feature to the editor #1330 +- ✨(backend) Comments on text editor #1330 + ### Fixed - ♿(frontend) improve accessibility: - ♿(frontend) improve share modal button accessibility #1626 +- 🐛(frontend) fix toolbar not activated when reader #1640 +- 🐛(frontend) preserve left panel width on window resize #1588 ## [3.10.0] - 2025-11-18 @@ -40,7 +47,6 @@ and this project adheres to ### Security - mitigate role escalation in the ask_for_access viewset #1580 -- 🐛(frontend) preserve left panel width on window resize #1588 ### Removed @@ -54,7 +60,6 @@ and this project adheres to - ✨(frontend) create skeleton component for DocEditor #1491 - ✨(frontend) add an EmojiPicker in the document tree and title #1381 - ✨(frontend) ajustable left panel #1456 -- ✨ Add comments feature to the editor #1330 ### Changed @@ -183,7 +188,6 @@ and this project adheres to ### Added -- ✨(backend) Comments on text editor #1309 - 👷(CI) add bundle size check job #1268 - ✨(frontend) use title first emoji as doc icon in tree #1289 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 47728dfca1..15a86db5fc 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -241,20 +241,66 @@ test.describe('Doc Editor', () => { await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible(); }); - test('it cannot edit if viewer', async ({ page }) => { - await mockedDocument(page, { - user_role: 'reader', - }); + test('it cannot edit if viewer but see and can get resources', async ({ + page, + browserName, + }) => { + const [docTitle] = await createDoc(page, 'doc-viewer', browserName, 1); + await verifyDocName(page, docTitle); - await goToGridDoc(page); + await writeInEditor({ page, text: 'Hello World' }); - const card = page.getByLabel('It is the card information'); - await expect(card).toBeVisible(); + await page.getByRole('button', { name: 'Share' }).click(); + await updateShareLink(page, 'Public', 'Reading'); + + // Close the modal + await page.getByRole('button', { name: 'close' }).first().click(); - await expect(card.getByText('Reader')).toBeVisible(); + const { otherPage, cleanup } = await connectOtherUserToDoc({ + browserName, + docUrl: page.url(), + withoutSignIn: true, + docTitle, + }); - const editor = page.locator('.ProseMirror'); + await expect( + otherPage.getByLabel('It is the card information').getByText('Reader'), + ).toBeVisible(); + + // Cannot edit + const editor = otherPage.locator('.ProseMirror'); await expect(editor).toHaveAttribute('contenteditable', 'false'); + + // Owner add a image + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.locator('.bn-block-outer').last().fill('/'); + await page.getByText('Resizable image with caption').click(); + await page.getByText('Upload image').click(); + + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles( + path.join(__dirname, 'assets/logo-suite-numerique.png'), + ); + + // Owner see the image + await expect( + page.locator('.--docs--editor-container img.bn-visual-media').first(), + ).toBeVisible(); + + // Viewser see the image + const viewerImg = otherPage + .locator('.--docs--editor-container img.bn-visual-media') + .first(); + await expect(viewerImg).toBeVisible(); + + // Viewer can download the image + await viewerImg.click(); + const downloadPromise = otherPage.waitForEvent('download'); + await otherPage.getByRole('button', { name: 'Download image' }).click(); + const download = await downloadPromise; + expect(download.suggestedFilename()).toBe('logo-suite-numerique.png'); + + await cleanup(); }); test('it adds an image to the doc editor', async ({ page, browserName }) => { 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 19e7991676..1c8b270115 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 @@ -289,11 +289,13 @@ export const BlockNoteReader = ({ editor={editor} editable={false} theme="light" - aria-label={t('Document version viewer')} + aria-label={t('Document viewer')} formattingToolbar={false} slashMenu={false} comments={false} - /> + > + + ); }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx index 3e30224b73..0934a23ec9 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx @@ -59,9 +59,14 @@ const UploadLoaderBlockComponent = ({ editor, }: UploadLoaderBlockComponentProps) => { const mediaUrl = useMediaUrl(); + const isEditable = editor.isEditable; useEffect(() => { - if (!block.props.blockUploadUrl || block.props.type !== 'loading') { + if ( + !block.props.blockUploadUrl || + block.props.type !== 'loading' || + !isEditable + ) { return; } @@ -108,7 +113,7 @@ const UploadLoaderBlockComponent = ({ /* During collaboration, another user might have updated the block */ } }); - }, [block, editor, mediaUrl]); + }, [block, editor, mediaUrl, isEditable]); return ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx index 28166de12d..846fa54abf 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx @@ -26,14 +26,19 @@ export const InterlinkingLinkInlineContent = createReactInlineContentSpec( content: 'none', }, { - render: ({ inlineContent, updateInlineContent }) => { + render: ({ editor, inlineContent, updateInlineContent }) => { const { data: doc } = useDoc({ id: inlineContent.props.docId }); + const isEditable = editor.isEditable; /** * Update the content title if the referenced doc title changes */ useEffect(() => { - if (doc?.title && doc.title !== inlineContent.props.title) { + if ( + isEditable && + doc?.title && + doc.title !== inlineContent.props.title + ) { updateInlineContent({ type: 'interlinkingLinkInline', props: { @@ -50,7 +55,7 @@ export const InterlinkingLinkInlineContent = createReactInlineContentSpec( * not when inlineContent.props.title changes. */ // eslint-disable-next-line react-hooks/exhaustive-deps - }, [doc?.title]); + }, [doc?.title, isEditable]); return ; }, diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx index b6a23b6b08..2edebee606 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx @@ -86,6 +86,7 @@ export const SearchPage = ({ const [search, setSearch] = useState(''); const { isDesktop } = useResponsiveStore(); const { untitledDocument } = useTrans(); + const isEditable = editor.isEditable; /** * createReactInlineContentSpec add automatically the focus after @@ -101,6 +102,10 @@ export const SearchPage = ({ }, [inputRef]); const closeSearch = (insertContent: string) => { + if (!isEditable) { + return; + } + updateInlineContent({ type: 'interlinkingSearchInline', props: { @@ -223,6 +228,10 @@ export const SearchPage = ({ search={search} filters={{ target: DocSearchTarget.CURRENT }} onSelect={(doc) => { + if (!isEditable) { + return; + } + updateInlineContent({ type: 'interlinkingSearchInline', props: {