Skip to content

Commit 71a5856

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 f8b8390 commit 71a5856

File tree

5 files changed

+83
-16
lines changed

5 files changed

+83
-16
lines changed

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: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,14 @@ 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+
if (
66+
!block.props.blockUploadUrl ||
67+
block.props.type !== 'loading' ||
68+
!isEditable
69+
) {
6570
return;
6671
}
6772

@@ -108,7 +113,7 @@ const UploadLoaderBlockComponent = ({
108113
/* During collaboration, another user might have updated the block */
109114
}
110115
});
111-
}, [block, editor, mediaUrl]);
116+
}, [block, editor, mediaUrl, isEditable]);
112117

113118
return (
114119
<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)