From 7e550a94756804b010538bd5230d00158561c0a8 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Thu, 16 Jul 2026 12:09:21 -0400 Subject: [PATCH] Chat and Review-panel mentions (#34564) * feat: changing chat to use cm6 editor instead to allow for mentions display and autocomplete * feat: changing review panel comment, edit, and reply to also use cm6 editor to enable mentions * feat: updating tests, and moving from jsdom to cypress tests as jsdom cant handle cm6 * feat: centralizing mentions component and applying to both chat and review-panel * feat: centralizing mentions input, and using changesUsers in chat mentions * fixing dark mode styles of chat * feat: updating mentions dropdown style and adding custom icon generation for mentions cm6 autocomplete tooltip * feat: making mentions look like links, and fixing border radius on tooltip * fix: naming and using correct color util * feat: fixing hover on highlight * update scroll bar dance in review panel when a system had persistent scroll bars, we would run into issues calculating the height of the review panel Co-authored-by: Kristina <7614497+khjrtbrg@users.noreply.github.com> --------- Co-authored-by: Kristina <7614497+khjrtbrg@users.noreply.github.com> GitOrigin-RevId: 96441ad55f9614427506edfecf3d87bae82d1607 --- .../web/frontend/extracted-translations.json | 2 + .../chat/components/message-content.tsx | 80 +++--- .../chat/components/message-input.tsx | 41 ++- .../ide-react/context/react-context-root.tsx | 10 +- .../components/review-panel-add-comment.tsx | 97 +++---- .../components/review-panel-change-user.tsx | 2 +- .../components/review-panel-change.tsx | 2 +- .../review-panel-comment-content.tsx | 72 +++-- .../components/review-panel-entry-user.tsx | 4 +- .../components/review-panel-entry.tsx | 13 +- .../review-panel-expandable-content.tsx | 4 +- .../components/review-panel-message.tsx | 58 ++-- .../context/review-panel-providers.tsx | 13 +- .../review-panel/utils/form-events.ts | 7 - .../source-editor/extensions/mentions.ts | 242 ++++++++++++++++ .../extensions/project-members-info.ts | 28 ++ .../components/mention-badge.tsx | 10 +- .../js/shared/components/mentions-input.tsx | 258 ++++++++++++++++++ .../context/changes-users-context.tsx | 2 +- .../shared/context/types/project-metadata.tsx | 18 +- .../utils/build-name.ts | 0 .../web/frontend/js/shared/utils/mentions.ts | 39 +++ .../utils/parse-mentions.ts | 4 +- .../stylesheets/pages/editor/chat.scss | 59 ++++ .../pages/editor/review-panel.scss | 80 ++++-- services/web/locales/en.json | 2 + .../chat/components/message-content.spec.tsx | 84 ++++++ .../chat/components/message-group.test.tsx | 28 +- .../chat/components/message-input.spec.tsx | 55 ++++ .../chat/components/message-input.test.tsx | 58 ---- .../review-panel/review-panel.spec.tsx | 18 +- .../components/mention-badge.test.tsx | 6 +- .../utils/parse-mentions.test.ts | 2 +- 33 files changed, 1056 insertions(+), 342 deletions(-) delete mode 100644 services/web/frontend/js/features/review-panel/utils/form-events.ts create mode 100644 services/web/frontend/js/features/source-editor/extensions/mentions.ts create mode 100644 services/web/frontend/js/features/source-editor/extensions/project-members-info.ts rename services/web/frontend/js/{features/review-panel => shared}/components/mention-badge.tsx (65%) create mode 100644 services/web/frontend/js/shared/components/mentions-input.tsx rename services/web/frontend/js/{features/review-panel => shared}/context/changes-users-context.tsx (97%) rename services/web/frontend/js/{features/review-panel => shared}/utils/build-name.ts (100%) create mode 100644 services/web/frontend/js/shared/utils/mentions.ts rename services/web/frontend/js/{features/review-panel => shared}/utils/parse-mentions.ts (93%) create mode 100644 services/web/test/frontend/features/chat/components/message-content.spec.tsx create mode 100644 services/web/test/frontend/features/chat/components/message-input.spec.tsx delete mode 100644 services/web/test/frontend/features/chat/components/message-input.test.tsx rename services/web/test/frontend/{features/review-panel => shared}/components/mention-badge.test.tsx (89%) rename services/web/test/frontend/{features/review-panel => shared}/utils/parse-mentions.test.ts (98%) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index c269736b82..0e8cfb2a5c 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -570,6 +570,7 @@ "each_user_will_have_access_to": "", "easily_manage_your_project_files_everywhere": "", "edit": "", + "edit_comment": "", "edit_comment_error_message": "", "edit_comment_error_title": "", "edit_content_directly": "", @@ -577,6 +578,7 @@ "edit_dictionary_empty": "", "edit_dictionary_remove": "", "edit_figure": "", + "edit_message": "", "edit_reference": "", "edit_sso_configuration": "", "edit_tag": "", diff --git a/services/web/frontend/js/features/chat/components/message-content.tsx b/services/web/frontend/js/features/chat/components/message-content.tsx index 4e60ec06dd..4ceaf37b29 100644 --- a/services/web/frontend/js/features/chat/components/message-content.tsx +++ b/services/web/frontend/js/features/chat/components/message-content.tsx @@ -6,7 +6,10 @@ import { debugConsole } from '@/utils/debugging' import { Message, useChatContext } from '@/features/chat/context/chat-context' import OLButton from '@/shared/components/ol/ol-button' import { useTranslation } from 'react-i18next' -import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area' +import { mentionsFeatureEnabled } from '@/shared/utils/mentions' +import { MentionsInput } from '@/shared/components/mentions-input' +import { parseMentions } from '@/shared/utils/parse-mentions' +import { MentionBadge } from '@/shared/components/mention-badge' const MessageContent: FC<{ content: Message['content'] @@ -47,64 +50,51 @@ const MessageContent: FC<{ } }, [content, mounted]) - const completeEdit = useCallback(() => { - editMessage(messageId, editedContent) - }, [editMessage, editedContent, messageId]) - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - e.preventDefault() - completeEdit() - } else if (e.key === 'Escape') { - e.preventDefault() - cancelMessageEdit() - setEditedContent(content) - } + const completeEdit = useCallback( + (value: string) => { + editMessage(messageId, value) }, - [cancelMessageEdit, completeEdit, content] + [editMessage, messageId] ) - const handleChange = useCallback( - (e: React.ChangeEvent) => { - setEditedContent(e.target.value) - }, - [] - ) + const handleCancel = useCallback(() => { + cancelMessageEdit() + setEditedContent(content) + }, [cancelMessageEdit, content]) - const handleAutoFocus = useCallback( - (textarea: HTMLTextAreaElement) => textarea.select(), - [] - ) + const mentionsEnabled = mentionsFeatureEnabled() return editing ? ( <> -
+ + {t('cancel')} + { - cancelMessageEdit() - setEditedContent(content) - }} + onClick={() => completeEdit(editedContent)} > - {t('cancel')} - - completeEdit()}> {t('save')} ) : (

- {content} + {mentionsEnabled ? ( + renderMentionedContent(content) + ) : ( + {content} + )} {edited ? ( <> {' '} @@ -115,4 +105,14 @@ const MessageContent: FC<{ ) } +function renderMentionedContent(content: string) { + return parseMentions(content).map((segment, index) => + segment.type === 'mention' ? ( + + ) : ( + {segment.value} + ) + ) +} + export default MessageContent diff --git a/services/web/frontend/js/features/chat/components/message-input.tsx b/services/web/frontend/js/features/chat/components/message-input.tsx index e781b72d55..71b74d58df 100644 --- a/services/web/frontend/js/features/chat/components/message-input.tsx +++ b/services/web/frontend/js/features/chat/components/message-input.tsx @@ -1,4 +1,9 @@ import { useTranslation } from 'react-i18next' +import { useCallback, useRef } from 'react' +import { + MentionsInput, + MentionsInputHandle, +} from '@/shared/components/mentions-input' type MessageInputProps = { resetUnreadMessages: () => void @@ -7,33 +12,25 @@ type MessageInputProps = { function MessageInput({ resetUnreadMessages, sendMessage }: MessageInputProps) { const { t } = useTranslation() + const inputRef = useRef(null) - function handleKeyDown(event: React.KeyboardEvent) { - const selectingCharacter = event.nativeEvent.isComposing - if (event.key === 'Enter' && !selectingCharacter) { - event.preventDefault() - const target = event.target as HTMLInputElement - sendMessage(target.value) - // wrap the form reset in setTimeout so input sources have time to finish - // https://github.com/overleaf/internal/pull/9206 - window.setTimeout(() => { - target.blur() - target.closest('form')?.reset() - target.focus() - }, 0) - } - } + const handleSubmit = useCallback( + (message: string) => { + sendMessage(message) + inputRef.current?.clear() + }, + [sendMessage] + ) return (

- -