️(frontend) improve Comments feature

Improve the comments feature to reduce annoyance:
- gives focus on input when opening comment threads
- hide comment button when mobile view
- improve contrast of overline commented text
- remove thread if last comment deleted
- scroll to bottom thread when adding new comment
This commit is contained in:
Anthony LC
2025-12-09 11:16:59 +01:00
parent 9fcc221b33
commit a6b472aa51
6 changed files with 113 additions and 10 deletions

View File

@@ -6,12 +6,16 @@ and this project adheres to
## [Unreleased]
### Added
- ⚡️(frontend) export html #1669
### Changed
- ♿(frontend) improve accessibility:
- ♿(frontend) add skip to content button for keyboard accessibility #1624
- ♿(frontend) fix toggle panel button a11y labels #1634
- ⚡️(frontend) Enhance/html copy to download #1669
- ⚡️(frontend) improve Comments feature #1687
### Fixed

View File

@@ -1,6 +1,11 @@
import { expect, test } from '@playwright/test';
import { createDoc, getOtherBrowserName, verifyDocName } from './utils-common';
import {
closeHeaderMenu,
createDoc,
getOtherBrowserName,
verifyDocName,
} from './utils-common';
import { writeInEditor } from './utils-editor';
import {
addNewMember,
@@ -116,8 +121,7 @@ test.describe('Doc Comments', () => {
await createDoc(page, 'comment-interaction', browserName, 1);
// Checks add react reaction
const editor = page.locator('.ProseMirror');
await editor.locator('.bn-block-outer').last().fill('Hello World');
const editor = await writeInEditor({ page, text: 'Hello' });
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Comment' }).click();
@@ -181,6 +185,28 @@ test.describe('Doc Comments', () => {
'background-color',
'rgba(0, 0, 0, 0)',
);
/* Delete the last comment remove the thread */
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Comment' }).click();
await thread.getByRole('paragraph').first().fill('This is a new comment');
await thread.locator('[data-test="save"]').click();
await expect(editor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(237, 180, 0, 0.4)',
);
await editor.getByText('Hello').click();
await thread.getByText('This is a new comment').first().hover();
await thread.locator('[data-test="moreactions"]').first().click();
await thread.getByRole('menuitem', { name: 'Delete comment' }).click();
await expect(editor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(0, 0, 0, 0)',
);
});
test('it checks the comments abilities', async ({ page, browserName }) => {
@@ -293,3 +319,27 @@ test.describe('Doc Comments', () => {
await cleanup();
});
});
test.describe('Doc Comments mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test('Comments are not visible on mobile', async ({ page, browserName }) => {
const [title] = await createDoc(
page,
'comment-mobile',
browserName,
1,
true,
);
await closeHeaderMenu(page);
await verifyDocName(page, title);
// Checks add react reaction
const editor = await writeInEditor({ page, text: 'Hello' });
await editor.getByText('Hello').selectText();
await expect(page.getByRole('button', { name: 'Comment' })).toBeHidden();
await expect(page.getByRole('button', { name: 'Paragraph' })).toBeVisible();
});
});

View File

@@ -1,3 +1,8 @@
/**
* This file is adapted from BlockNote's AddCommentButton component
* https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/DefaultButtons/AddCommentButton.tsx
*/
import {
useBlockNoteEditor,
useComponentsContext,
@@ -10,6 +15,7 @@ import { css } from 'styled-components';
import { Box, Icon } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useDocStore } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import {
DocsBlockSchema,
@@ -22,6 +28,7 @@ export const CommentToolbarButton = () => {
const { currentDoc } = useDocStore();
const { t } = useTranslation();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const editor = useBlockNoteEditor<
DocsBlockSchema,
@@ -35,7 +42,18 @@ export const CommentToolbarButton = () => {
return !!selectedBlocks.find((block) => block.content !== undefined);
}, [selectedBlocks]);
const focusOnInputThread = () => {
// Use setTimeout to ensure the DOM has been updated with the new comment
setTimeout(() => {
const threadElement = document.querySelector<HTMLElement>(
'.bn-thread .bn-editor',
);
threadElement?.focus();
}, 400);
};
if (
!isDesktop ||
!show ||
!editor.isEditable ||
!Components ||
@@ -51,6 +69,7 @@ export const CommentToolbarButton = () => {
onClick={() => {
editor.comments?.startPendingComment();
editor.formattingToolbar.closeMenu();
focusOnInputThread();
}}
aria-haspopup="dialog"
data-test="comment-toolbar-button"

View File

@@ -117,6 +117,21 @@ export class DocsThreadStore extends ThreadStore {
});
}
/**
* Scrolls to the bottom of a thread modal
* @param threadId
*/
private scrollToBottomOfThread() {
// Use setTimeout to ensure the DOM has been updated with the new comment
setTimeout(() => {
const threadElement = document.querySelector('.bn-thread');
threadElement?.scrollBy({
top: threadElement.scrollHeight,
behavior: 'smooth',
});
}, 200);
}
/**
* Notifies all subscribers about the current thread state
*/
@@ -345,6 +360,10 @@ export class DocsThreadStore extends ThreadStore {
await this.refreshThread(threadId);
}
this.ping(threadId);
// Auto-scroll to bottom of thread after adding comment
this.scrollToBottomOfThread();
return serverCommentToClientComment(comment);
};
@@ -405,10 +424,20 @@ export class DocsThreadStore extends ThreadStore {
// Optimistically remove the comment locally if we have the thread
const existing = this.threads.get(threadId);
if (existing) {
const updatedComments = existing.comments.filter(
(c) => c.id !== commentId,
);
// If this was the last comment, delete the thread
if (updatedComments.length === 0) {
await this.deleteThread({ threadId });
return;
}
const updated: ClientThreadData = {
...existing,
updatedAt: new Date(),
comments: existing.comments.filter((c) => c.id !== commentId),
comments: updatedComments,
};
this.upsertClientThreadData(updated);
this.notifySubscribers();
@@ -419,10 +448,6 @@ export class DocsThreadStore extends ThreadStore {
this.ping(threadId);
};
/**
* UI not implemented
* @param _options
*/
public deleteThread = async (_options: { threadId: string }) => {
const response = await fetchAPI(
`documents/${this.docId}/threads/${_options.threadId}/`,

View File

@@ -13,6 +13,10 @@ export const cssComments = (
background: ${canSeeComment ? '#EDB40066' : 'transparent'};
color: var(--c--globals--colors--gray-700);
}
[data-show-selection] {
color: HighlightText;
}
}
em-emoji-picker {

View File

@@ -4,12 +4,13 @@ import { css } from 'styled-components';
import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, useDocUtils, useTrans } from '@/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import ChildDocument from '../assets/child-document.svg';
import PinnedDocumentIcon from '../assets/pinned-document.svg';
import SimpleFileIcon from '../assets/simple-document.svg';
import { useDocUtils, useTrans } from '../hooks';
import { Doc } from '../types';
const ItemTextCss = css`
overflow: hidden;