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: {