diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e005de820..e0603aeb42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts index b791bdcc38..ce2490e4c5 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts @@ -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(); + }); +}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/CommentToolbarButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/CommentToolbarButton.tsx index 1aaaa5e3c7..6fa3f2209d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/CommentToolbarButton.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/CommentToolbarButton.tsx @@ -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( + '.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" diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx index f09c20b255..b9b71bea5c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx @@ -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}/`, diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx index 0a87f63201..e2a59a8c4d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/styles.tsx @@ -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 { diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx index 6b4be46b0d..8873f76b39 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx @@ -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;