From 4e2840e247cec27fb74b65e07325ffb925a03262 Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:32:23 +0200 Subject: [PATCH] [web] remove mentions from chat message editing (#35761) GitOrigin-RevId: 6cd27490485196b82a9c4d9ffa19ba5d8e10922d --- .../web/frontend/extracted-translations.json | 1 - .../chat/components/message-content.tsx | 80 +++++++++--------- .../js/shared/components/mentions-input.tsx | 43 +--------- .../stylesheets/pages/editor/chat.scss | 59 ------------- services/web/locales/en.json | 1 - .../chat/components/message-content.spec.tsx | 84 ------------------- .../chat/components/message-group.test.tsx | 28 +++---- 7 files changed, 56 insertions(+), 240 deletions(-) delete mode 100644 services/web/test/frontend/features/chat/components/message-content.spec.tsx diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index ff9ce990a1..081ad605cd 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -583,7 +583,6 @@ "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 4ceaf37b29..4e60ec06dd 100644 --- a/services/web/frontend/js/features/chat/components/message-content.tsx +++ b/services/web/frontend/js/features/chat/components/message-content.tsx @@ -6,10 +6,7 @@ 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 { 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' +import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area' const MessageContent: FC<{ content: Message['content'] @@ -50,51 +47,64 @@ const MessageContent: FC<{ } }, [content, mounted]) - const completeEdit = useCallback( - (value: string) => { - editMessage(messageId, value) + 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) + } }, - [editMessage, messageId] + [cancelMessageEdit, completeEdit, content] ) - const handleCancel = useCallback(() => { - cancelMessageEdit() - setEditedContent(content) - }, [cancelMessageEdit, content]) + const handleChange = useCallback( + (e: React.ChangeEvent) => { + setEditedContent(e.target.value) + }, + [] + ) - const mentionsEnabled = mentionsFeatureEnabled() + const handleAutoFocus = useCallback( + (textarea: HTMLTextAreaElement) => textarea.select(), + [] + ) return editing ? ( <> -
- - {t('cancel')} - completeEdit(editedContent)} + onClick={() => { + cancelMessageEdit() + setEditedContent(content) + }} > + {t('cancel')} + + completeEdit()}> {t('save')} ) : (

- {mentionsEnabled ? ( - renderMentionedContent(content) - ) : ( - {content} - )} + {content} {edited ? ( <> {' '} @@ -105,14 +115,4 @@ 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/shared/components/mentions-input.tsx b/services/web/frontend/js/shared/components/mentions-input.tsx index 433c955403..cc5e80ab95 100644 --- a/services/web/frontend/js/shared/components/mentions-input.tsx +++ b/services/web/frontend/js/shared/components/mentions-input.tsx @@ -6,7 +6,7 @@ import { drawSelection, placeholder as placeholderExt, } from '@codemirror/view' -import { Compartment, EditorSelection } from '@codemirror/state' +import { Compartment } from '@codemirror/state' import { defaultKeymap, history, historyKeymap } from '@codemirror/commands' import { autocompletion, completionStatus } from '@codemirror/autocomplete' import classNames from 'classnames' @@ -27,7 +27,7 @@ export type MentionsInputHandle = { // Returns true if the editor was focused, false if the view isn't ready yet. focus: () => boolean // Empties the editor. Used by inputs that stay mounted between submissions - // (the chat composer and the review-panel reply box). + // (the review-panel reply box). clear: () => void } @@ -35,16 +35,11 @@ type MentionsInputProps = { onSubmit: (value: string) => void onChange?: (value: string) => void onBlur?: (value: string) => void - // Called on Escape (when the autocomplete popup is not open). Used by the - // edit flows to cancel without saving. - onCancel?: () => void - onFocus?: () => void placeholder?: string label?: string disabled?: boolean initialValue?: string autoFocus?: boolean - selectOnFocus?: boolean className?: string } @@ -73,8 +68,8 @@ const mentionsInputTheme = EditorView.theme({ }) // A small CodeMirror 6 input that supports `@mention` autocompletion. Shared by -// the chat composer/edit field and the review panel's add-comment, reply, and -// edit fields so all of them get the same mention behaviour. +// the review panel's add-comment, reply, and edit fields so all of them get the +// same mention behaviour. export const MentionsInput = forwardRef< MentionsInputHandle, MentionsInputProps @@ -83,14 +78,11 @@ export const MentionsInput = forwardRef< onChange, onSubmit, onBlur, - onCancel, - onFocus, placeholder = '', label, disabled = false, initialValue = '', autoFocus = false, - selectOnFocus = false, className, }, ref @@ -108,14 +100,10 @@ export const MentionsInput = forwardRef< const onChangeRef = useRef(onChange) const onSubmitRef = useRef(onSubmit) const onBlurRef = useRef(onBlur) - const onCancelRef = useRef(onCancel) - const onFocusRef = useRef(onFocus) const disabledRef = useRef(disabled) onChangeRef.current = onChange onSubmitRef.current = onSubmit onBlurRef.current = onBlur - onCancelRef.current = onCancel - onFocusRef.current = onFocus disabledRef.current = disabled useImperativeHandle( @@ -182,10 +170,6 @@ export const MentionsInput = forwardRef< } }), EditorView.domEventHandlers({ - focus: () => { - onFocusRef.current?.() - return false - }, blur: (_event, view) => { if (completionStatus(view.state) === 'active') { return false @@ -205,20 +189,6 @@ export const MentionsInput = forwardRef< return true }, }, - { - key: 'Escape', - run: view => { - // Let the autocomplete popup handle Escape when it's open. - if (completionStatus(view.state) === 'active') { - return false - } - if (onCancelRef.current) { - onCancelRef.current() - return true - } - return false - }, - }, ...defaultKeymap, ...historyKeymap, ]), @@ -228,11 +198,6 @@ export const MentionsInput = forwardRef< viewRef.current = view if (autoFocus) { view.focus() - if (selectOnFocus) { - view.dispatch({ - selection: EditorSelection.range(0, view.state.doc.length), - }) - } } return () => { diff --git a/services/web/frontend/stylesheets/pages/editor/chat.scss b/services/web/frontend/stylesheets/pages/editor/chat.scss index 6bea504b2d..8439d16461 100644 --- a/services/web/frontend/stylesheets/pages/editor/chat.scss +++ b/services/web/frontend/stylesheets/pages/editor/chat.scss @@ -90,35 +90,6 @@ padding: calc(var(--line-height-03) / 4); background-color: var(--chat-new-message-textarea-bg); } - - .chat-message-input { - height: 100%; - width: 100%; - - .cm-editor { - height: 100%; - border-radius: var(--border-radius-base); - border: 1px solid var(--border-divider-themed); - background-color: var(--chat-new-message-textarea-bg); - - &.cm-focused { - // suppress CM6's default focus outline; the border conveys focus - outline: none; - } - } - - .cm-content { - color: var(--chat-new-message-textarea-color); - font-size: var(--font-size-02); - line-height: var(--line-height-02); - padding: calc(var(--line-height-03) / 4); - } - - .cm-cursor, - .cm-cursor-primary { - border-left-color: var(--chat-new-message-textarea-color); - } - } } } @@ -250,36 +221,6 @@ font-weight: normal; } - - .mention-badge { - color: var(--content-info); - } - - .chat-message-edit-input { - width: 100%; - - .cm-editor { - border-radius: var(--border-radius-base); - border: 1px solid var(--border-divider-themed); - background-color: var(--chat-new-message-textarea-bg); - - &.cm-focused { - outline: none; - } - } - - .cm-content { - color: var(--chat-new-message-textarea-color); - font-size: var(--font-size-02); - line-height: var(--line-height-02); - padding: calc(var(--line-height-03) / 4); - } - - .cm-cursor, - .cm-cursor-primary { - border-left-color: var(--chat-new-message-textarea-color); - } - } } .message-container.message-from-self { diff --git a/services/web/locales/en.json b/services/web/locales/en.json index bf6da2990f..8bc3256610 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -753,7 +753,6 @@ "edit_dictionary_empty": "Your custom dictionary is empty.", "edit_dictionary_remove": "Remove from dictionary", "edit_figure": "Edit figure", - "edit_message": "Edit message", "edit_reference": "Edit reference", "edit_sso_configuration": "Edit SSO Configuration", "edit_tag": "Edit tag", diff --git a/services/web/test/frontend/features/chat/components/message-content.spec.tsx b/services/web/test/frontend/features/chat/components/message-content.spec.tsx deleted file mode 100644 index 42d4ceb5c1..0000000000 --- a/services/web/test/frontend/features/chat/components/message-content.spec.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import ChatPane from '@/features/chat/components/chat-pane' -import { - EditorProviders, - makeProjectProvider, - USER_ID, - USER_EMAIL, -} from '../../../helpers/editor-providers' -import { mockProject } from '../../source-editor/helpers/mock-project' - -describe(' editing', function () { - const message = { - id: 'msg_1', - content: 'hello world', - user: { - id: USER_ID, - first_name: 'Test', - last_name: 'User', - email: USER_EMAIL, - }, - timestamp: new Date('2025-01-01T00:00:00.000Z').getTime(), - } - - beforeEach(function () { - window.metaAttributesCache.set('ol-preventCompileOnLoad', true) - window.metaAttributesCache.set('ol-splitTestVariants', { - 'chat-edit-delete': 'enabled', - }) - - cy.intercept('GET', '/project/*/messages*', [message]).as('getMessages') - cy.intercept('POST', '/project/*/messages/msg_1/edit', {}).as('editMessage') - - cy.mount( - - - - ) - - cy.wait('@getMessages') - cy.findByText('hello world') - }) - - function openEditor() { - cy.get('.message-dropdown-menu-btn').click({ force: true }) - cy.findByText('Edit').click({ force: true }) - return cy.findByRole('textbox', { name: 'Edit message' }) - } - - it('saves an edited message on Enter', function () { - openEditor().type('{selectAll}edited message{enter}', { force: true }) - - cy.wait('@editMessage') - .its('request.body.content') - .should('equal', 'edited message') - }) - - it('saves an edited message with the Save button', function () { - openEditor().type('{selectAll}edited message', { force: true }) - cy.findByRole('button', { name: 'Save' }).click({ force: true }) - - cy.wait('@editMessage') - .its('request.body.content') - .should('equal', 'edited message') - }) - - it('cancels editing on Escape without saving', function () { - openEditor().type('{selectAll}discarded{esc}', { force: true }) - - // Editor closes and the original message is shown again. - cy.findByRole('textbox', { name: 'Edit message' }).should('not.exist') - cy.findByText('hello world').should('exist') - cy.get('@editMessage.all').should('have.length', 0) - }) - - it('cancels editing with the Cancel button', function () { - openEditor().type('{selectAll}discarded', { force: true }) - cy.findByRole('button', { name: 'Cancel' }).click({ force: true }) - - cy.findByRole('textbox', { name: 'Edit message' }).should('not.exist') - cy.findByText('hello world').should('exist') - cy.get('@editMessage.all').should('have.length', 0) - }) -}) diff --git a/services/web/test/frontend/features/chat/components/message-group.test.tsx b/services/web/test/frontend/features/chat/components/message-group.test.tsx index 8dac3a60d5..a015e38b07 100644 --- a/services/web/test/frontend/features/chat/components/message-group.test.tsx +++ b/services/web/test/frontend/features/chat/components/message-group.test.tsx @@ -12,8 +12,6 @@ import { UserProvider } from '@/shared/context/user-context' import { ModalsContextProvider } from '@/features/ide-react/context/modals-context' import { SplitTestProvider } from '@/shared/context/split-test-context' -import { EditorProviders } from '../../../helpers/editor-providers' - describe('', function () { function ChatProviders({ children, @@ -29,17 +27,15 @@ describe('', function () { } return ( - - - - - - {children} - - - - - + + + + + {children} + + + + ) } @@ -156,7 +152,7 @@ describe('', function () { expect(screen.queryByText('(edited)')).to.not.exist }) - it('renders message being edited with a CodeMirror input and action buttons', function () { + it('renders message being edited with textarea and action buttons', function () { const messageBeingEdited: MessageType = { content: 'original message content', user: currentUser, @@ -170,8 +166,8 @@ describe('', function () { ) - const editor = screen.getByRole('textbox', { name: 'Edit message' }) - expect(editor.textContent).to.contain('original message content') + const textarea = screen.getByDisplayValue('original message content') + expect(textarea.tagName.toLowerCase()).to.equal('textarea') screen.getByRole('button', { name: 'Cancel' }) screen.getByRole('button', { name: 'Save' })