Skip to content

Commit f7baf23

Browse files
committed
🐛(frontend) fix toolbar not activated when reader
When user was a reader of the document, the toolbar of the BlockNote editor was not activated, making it impossible to download resources like images. We add the toolbar even in viewer mode. We block as well automatic document mutation from custom blocks when the editor is in viewer mode to avoid unwanted modifications.
1 parent bab42ef commit f7baf23

File tree

6 files changed

+91
-19
lines changed

6 files changed

+91
-19
lines changed

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ and this project adheres to
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- ✨ Add comments feature to the editor #1330
12+
- ✨(backend) Comments on text editor #1330
13+
914
### Fixed
1015

1116
- ♿(frontend) improve accessibility:
1217
- ♿(frontend) improve share modal button accessibility #1626
18+
- 🐛(frontend) fix toolbar not activated when reader #1640
19+
- 🐛(frontend) preserve left panel width on window resize #1588
1320

1421
## [3.10.0] - 2025-11-18
1522

@@ -40,7 +47,6 @@ and this project adheres to
4047
### Security
4148

4249
- mitigate role escalation in the ask_for_access viewset #1580
43-
- 🐛(frontend) preserve left panel width on window resize #1588
4450

4551
### Removed
4652

@@ -54,7 +60,6 @@ and this project adheres to
5460
- ✨(frontend) create skeleton component for DocEditor #1491
5561
- ✨(frontend) add an EmojiPicker in the document tree and title #1381
5662
- ✨(frontend) ajustable left panel #1456
57-
- ✨ Add comments feature to the editor #1330
5863

5964
### Changed
6065

@@ -183,7 +188,6 @@ and this project adheres to
183188

184189
### Added
185190

186-
- ✨(backend) Comments on text editor #1309
187191
- 👷(CI) add bundle size check job #1268
188192
- ✨(frontend) use title first emoji as doc icon in tree #1289
189193

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -241,20 +241,66 @@ test.describe('Doc Editor', () => {
241241
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
242242
});
243243

244-
test('it cannot edit if viewer', async ({ page }) => {
245-
await mockedDocument(page, {
246-
user_role: 'reader',
247-
});
244+
test('it cannot edit if viewer but see and can get resources', async ({
245+
page,
246+
browserName,
247+
}) => {
248+
const [docTitle] = await createDoc(page, 'doc-viewer', browserName, 1);
249+
await verifyDocName(page, docTitle);
248250

249-
await goToGridDoc(page);
251+
await writeInEditor({ page, text: 'Hello World' });
250252

251-
const card = page.getByLabel('It is the card information');
252-
await expect(card).toBeVisible();
253+
await page.getByRole('button', { name: 'Share' }).click();
254+
await updateShareLink(page, 'Public', 'Reading');
255+
256+
// Close the modal
257+
await page.getByRole('button', { name: 'close' }).first().click();
253258

254-
await expect(card.getByText('Reader')).toBeVisible();
259+
const { otherPage, cleanup } = await connectOtherUserToDoc({
260+
browserName,
261+
docUrl: page.url(),
262+
withoutSignIn: true,
263+
docTitle,
264+
});
255265

256-
const editor = page.locator('.ProseMirror');
266+
await expect(
267+
otherPage.getByLabel('It is the card information').getByText('Reader'),
268+
).toBeVisible();
269+
270+
// Cannot edit
271+
const editor = otherPage.locator('.ProseMirror');
257272
await expect(editor).toHaveAttribute('contenteditable', 'false');
273+
274+
// Owner add a image
275+
const fileChooserPromise = page.waitForEvent('filechooser');
276+
await page.locator('.bn-block-outer').last().fill('/');
277+
await page.getByText('Resizable image with caption').click();
278+
await page.getByText('Upload image').click();
279+
280+
const fileChooser = await fileChooserPromise;
281+
await fileChooser.setFiles(
282+
path.join(__dirname, 'assets/logo-suite-numerique.png'),
283+
);
284+
285+
// Owner see the image
286+
await expect(
287+
page.locator('.--docs--editor-container img.bn-visual-media').first(),
288+
).toBeVisible();
289+
290+
// Viewser see the image
291+
const viewerImg = otherPage
292+
.locator('.--docs--editor-container img.bn-visual-media')
293+
.first();
294+
await expect(viewerImg).toBeVisible();
295+
296+
// Viewer can download the image
297+
await viewerImg.click();
298+
const downloadPromise = otherPage.waitForEvent('download');
299+
await otherPage.getByRole('button', { name: 'Download image' }).click();
300+
const download = await downloadPromise;
301+
expect(download.suggestedFilename()).toBe('logo-suite-numerique.png');
302+
303+
await cleanup();
258304
});
259305

260306
test('it adds an image to the doc editor', async ({ page, browserName }) => {

src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,13 @@ export const BlockNoteReader = ({
289289
editor={editor}
290290
editable={false}
291291
theme="light"
292-
aria-label={t('Document version viewer')}
292+
aria-label={t('Document viewer')}
293293
formattingToolbar={false}
294294
slashMenu={false}
295295
comments={false}
296-
/>
296+
>
297+
<BlockNoteToolbar />
298+
</BlockNoteView>
297299
</Box>
298300
);
299301
};

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,15 @@ const UploadLoaderBlockComponent = ({
5959
editor,
6060
}: UploadLoaderBlockComponentProps) => {
6161
const mediaUrl = useMediaUrl();
62+
const isEditable = editor.isEditable;
6263

6364
useEffect(() => {
64-
if (!block.props.blockUploadUrl || block.props.type !== 'loading') {
65+
const shouldCheckStatus =
66+
block.props.blockUploadUrl &&
67+
block.props.type === 'loading' &&
68+
isEditable;
69+
70+
if (!shouldCheckStatus) {
6571
return;
6672
}
6773

@@ -108,7 +114,7 @@ const UploadLoaderBlockComponent = ({
108114
/* During collaboration, another user might have updated the block */
109115
}
110116
});
111-
}, [block, editor, mediaUrl]);
117+
}, [block, editor, mediaUrl, isEditable]);
112118

113119
return (
114120
<Box className="bn-visual-media-wrapper" $direction="row" $gap="0.5rem">

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,19 @@ export const InterlinkingLinkInlineContent = createReactInlineContentSpec(
2626
content: 'none',
2727
},
2828
{
29-
render: ({ inlineContent, updateInlineContent }) => {
29+
render: ({ editor, inlineContent, updateInlineContent }) => {
3030
const { data: doc } = useDoc({ id: inlineContent.props.docId });
31+
const isEditable = editor.isEditable;
3132

3233
/**
3334
* Update the content title if the referenced doc title changes
3435
*/
3536
useEffect(() => {
36-
if (doc?.title && doc.title !== inlineContent.props.title) {
37+
if (
38+
isEditable &&
39+
doc?.title &&
40+
doc.title !== inlineContent.props.title
41+
) {
3742
updateInlineContent({
3843
type: 'interlinkingLinkInline',
3944
props: {
@@ -50,7 +55,7 @@ export const InterlinkingLinkInlineContent = createReactInlineContentSpec(
5055
* not when inlineContent.props.title changes.
5156
*/
5257
// eslint-disable-next-line react-hooks/exhaustive-deps
53-
}, [doc?.title]);
58+
}, [doc?.title, isEditable]);
5459

5560
return <LinkSelected {...inlineContent.props} />;
5661
},

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const SearchPage = ({
8686
const [search, setSearch] = useState('');
8787
const { isDesktop } = useResponsiveStore();
8888
const { untitledDocument } = useTrans();
89+
const isEditable = editor.isEditable;
8990

9091
/**
9192
* createReactInlineContentSpec add automatically the focus after
@@ -101,6 +102,10 @@ export const SearchPage = ({
101102
}, [inputRef]);
102103

103104
const closeSearch = (insertContent: string) => {
105+
if (!isEditable) {
106+
return;
107+
}
108+
104109
updateInlineContent({
105110
type: 'interlinkingSearchInline',
106111
props: {
@@ -223,6 +228,10 @@ export const SearchPage = ({
223228
search={search}
224229
filters={{ target: DocSearchTarget.CURRENT }}
225230
onSelect={(doc) => {
231+
if (!isEditable) {
232+
return;
233+
}
234+
226235
updateInlineContent({
227236
type: 'interlinkingSearchInline',
228237
props: {

0 commit comments

Comments
 (0)