Skip to content

Commit 2a61008

Browse files
committed
✨(frontend) add comments feature
Implemented the comments feature for the document editor. We are now able to add, view, and manage comments within the document editor interface.
1 parent 7a1b76a commit 2a61008

File tree

10 files changed

+863
-6
lines changed

10 files changed

+863
-6
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { chromium, expect, test } from '@playwright/test';
2+
3+
import {
4+
BROWSERS,
5+
createDoc,
6+
keyCloakSignIn,
7+
verifyDocName,
8+
} from './utils-common';
9+
import { addNewMember } from './utils-share';
10+
11+
test.beforeEach(async ({ page }) => {
12+
await page.goto('/');
13+
});
14+
15+
test.describe('Doc Comments', () => {
16+
test('it checks comments with 2 users in real time', async ({
17+
page,
18+
browserName,
19+
}) => {
20+
const [docTitle] = await createDoc(page, 'comment-doc', browserName, 1);
21+
22+
// We share the doc with another user
23+
const otherBrowserName = BROWSERS.find((b) => b !== browserName);
24+
if (!otherBrowserName) {
25+
throw new Error('No alternative browser found');
26+
}
27+
await page.getByRole('button', { name: 'Share' }).click();
28+
await addNewMember(page, 1, 'Administrator', otherBrowserName);
29+
30+
await expect(
31+
page
32+
.getByRole('listbox', { name: 'Suggestions' })
33+
.getByText(new RegExp(otherBrowserName)),
34+
).toBeVisible();
35+
36+
await page.getByRole('button', { name: 'close' }).click();
37+
38+
// We add a comment with the first user
39+
const editor = page.locator('.ProseMirror');
40+
await editor.locator('.bn-block-outer').last().fill('Hello World');
41+
await editor.getByText('Hello').selectText();
42+
await page.getByRole('button', { name: 'Add comment' }).click();
43+
44+
const thread = page.locator('.bn-thread');
45+
await thread.getByRole('paragraph').first().fill('This is a comment');
46+
await thread.locator('[data-test="save"]').click();
47+
await editor.getByText('Hello').click();
48+
49+
await expect(thread.getByText('This is a comment').first()).toBeVisible();
50+
await expect(thread.getByText(`E2E ${browserName}`).first()).toBeVisible();
51+
52+
const urlCommentDoc = page.url();
53+
54+
/**
55+
* We open another browser that will connect to the collaborative server
56+
* and will block the current browser to edit the doc.
57+
*/
58+
const otherBrowser = await chromium.launch({ headless: true });
59+
const otherContext = await otherBrowser.newContext({
60+
locale: 'en-US',
61+
timezoneId: 'Europe/Paris',
62+
permissions: [],
63+
storageState: {
64+
cookies: [],
65+
origins: [],
66+
},
67+
});
68+
const otherPage = await otherContext.newPage();
69+
await otherPage.goto(urlCommentDoc);
70+
71+
await otherPage.getByRole('button', { name: 'Login' }).click({
72+
timeout: 15000,
73+
});
74+
75+
await keyCloakSignIn(otherPage, otherBrowserName, false);
76+
77+
await verifyDocName(otherPage, docTitle);
78+
79+
const otherEditor = otherPage.locator('.ProseMirror');
80+
await otherEditor.getByText('Hello').click();
81+
const otherThread = otherPage.locator('.bn-thread');
82+
83+
// We check that the comment made by the first user is visible for the second user
84+
await expect(
85+
otherThread.getByText('This is a comment').first(),
86+
).toBeVisible();
87+
await expect(
88+
otherThread.getByText(`E2E ${browserName}`).first(),
89+
).toBeVisible();
90+
91+
// We add a comment with the second user
92+
await otherThread
93+
.getByRole('paragraph')
94+
.last()
95+
.fill('This is a comment from the other user');
96+
await otherThread.locator('[data-test="save"]').click();
97+
98+
// We check that the second user can see his comment he just made
99+
await expect(
100+
otherThread.getByText('This is a comment from the other user').first(),
101+
).toBeVisible();
102+
await expect(
103+
otherThread.getByText(`E2E ${otherBrowserName}`).first(),
104+
).toBeVisible();
105+
106+
// We check that the first user can see the comment made by the second user in real time
107+
await expect(
108+
thread.getByText('This is a comment from the other user').first(),
109+
).toBeVisible();
110+
await expect(
111+
thread.getByText(`E2E ${otherBrowserName}`).first(),
112+
).toBeVisible();
113+
});
114+
115+
test('it checks the comments interactions', async ({
116+
page,
117+
browserName,
118+
}) => {});
119+
});

src/frontend/apps/impress/src/features/auth/api/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ export interface User {
1313
short_name: string;
1414
language?: string;
1515
}
16+
17+
export type UserLight = Pick<User, 'full_name' | 'short_name'>;

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

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useCreateBlockNote } from '@blocknote/react';
1313
import { HocuspocusProvider } from '@hocuspocus/provider';
1414
import { useEffect } from 'react';
1515
import { useTranslation } from 'react-i18next';
16+
import { css } from 'styled-components';
1617
import * as Y from 'yjs';
1718

1819
import { Box, TextErrors } from '@/components';
@@ -33,6 +34,7 @@ import { randomColor } from '../utils';
3334

3435
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
3536
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
37+
import { cssComments, useComments } from './comments/';
3638
import {
3739
AccessibleImageBlock,
3840
CalloutBlock,
@@ -79,6 +81,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
7981
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
8082
const isConnectedToCollabServer = provider.isSynced;
8183
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;
84+
const canSeeComment = doc.abilities.comment;
8285

8386
useSaveDoc(doc.id, provider.document, !readOnly, isConnectedToCollabServer);
8487
const { i18n } = useTranslation();
@@ -91,6 +94,8 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
9194
: user?.full_name || user?.email || t('Anonymous');
9295
const showCursorLabels: 'always' | 'activity' | (string & {}) = 'activity';
9396

97+
const threadStore = useComments(provider.document, doc, user);
98+
9499
const editor: DocsBlockNoteEditor = useCreateBlockNote(
95100
{
96101
codeBlock,
@@ -143,11 +148,25 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
143148
},
144149
showCursorLabels: showCursorLabels as 'always' | 'activity',
145150
},
151+
comments: { threadStore },
146152
dictionary: {
147153
...locales[lang as keyof typeof locales],
148154
multi_column:
149155
multiColumnLocales?.[lang as keyof typeof multiColumnLocales],
150156
},
157+
resolveUsers: async (userIds) => {
158+
return Promise.resolve(
159+
userIds.map((encodedURIUserId) => {
160+
const fullName = decodeURIComponent(encodedURIUserId);
161+
162+
return {
163+
id: encodedURIUserId,
164+
username: fullName,
165+
avatarUrl: 'https://i.pravatar.cc/300',
166+
};
167+
}),
168+
);
169+
},
151170
tables: {
152171
splitCells: true,
153172
cellBackgroundColor: true,
@@ -157,7 +176,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
157176
uploadFile,
158177
schema: blockNoteSchema,
159178
},
160-
[collabName, lang, provider, uploadFile],
179+
[collabName, lang, provider, uploadFile, threadStore],
161180
);
162181

163182
useHeadings(editor);
@@ -176,7 +195,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
176195
<Box
177196
$padding={{ top: 'md' }}
178197
$background="white"
179-
$css={cssEditor(readOnly)}
198+
$css={css`
199+
${cssEditor(readOnly)};
200+
${cssComments(canSeeComment)}
201+
`}
180202
className="--docs--editor-container"
181203
>
182204
{errorAttachment && (
@@ -190,11 +212,13 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
190212
)}
191213

192214
<BlockNoteView
215+
className="--docs--main-editor"
193216
editor={editor}
194217
formattingToolbar={false}
195218
slashMenu={false}
196219
editable={!readOnly}
197220
theme="light"
221+
comments={canSeeComment}
198222
>
199223
<BlockNoteSuggestionMenu />
200224
<BlockNoteToolbar />
@@ -228,7 +252,12 @@ export const BlockNoteEditorVersion = ({
228252

229253
return (
230254
<Box $css={cssEditor(readOnly)} className="--docs--editor-container">
231-
<BlockNoteView editor={editor} editable={!readOnly} theme="light" />
255+
<BlockNoteView
256+
className="--docs--main-editor"
257+
editor={editor}
258+
editable={!readOnly}
259+
theme="light"
260+
/>
232261
</Box>
233262
);
234263
};

0 commit comments

Comments
 (0)