diff --git a/CHANGELOG.md b/CHANGELOG.md index 96791f3cd..3b7ec5ddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to - 🐛(frontend) fix duplicate document entries in grid #1479 - 🐛(frontend) show full nested doc names with ajustable bar #1456 +- 🐛(frontend) preserve @ character when esc is pressed after typing it #1512 ## [3.8.2] - 2025-10-17 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 46722adce..22b94882e 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -784,6 +784,29 @@ test.describe('Doc Editor', () => { ).toBeVisible(); }); + test('it keeps @ when pressing Escape', async ({ page, browserName }) => { + const [randomDoc] = await createDoc( + page, + 'doc-interlink-esc', + browserName, + 1, + ); + + await verifyDocName(page, randomDoc); + + const editor = await getEditor({ page }); + await page.keyboard.press('@'); + + const searchInput = page.locator( + "span[data-inline-content-type='interlinkingSearchInline'] input", + ); + await expect(searchInput).toBeVisible(); + + await page.keyboard.press('Escape'); + + await expect(editor.getByText('@')).toBeVisible(); + }); + test('it checks multiple big doc scroll to the top', async ({ page, browserName, diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx index 757579e53..1ecf90f3b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx @@ -3,6 +3,7 @@ import { StyleSchema, } from '@blocknote/core'; import { useBlockNoteEditor } from '@blocknote/react'; +import type { KeyboardEvent } from 'react'; import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -98,6 +99,55 @@ export const SearchPage = ({ }, 100); }, [inputRef]); + const closeSearch = (insertContent: string) => { + updateInlineContent({ + type: 'interlinkingSearchInline', + props: { + disabled: true, + trigger, + }, + }); + + contentRef(null); + editor.focus(); + editor.insertInlineContent([insertContent]); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + // Keep the trigger character ('@' or '/') in the editor when closing with Escape + closeSearch(trigger); + } else if (e.key === 'Backspace' && search.length === 0) { + e.preventDefault(); + closeSearch(''); + } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + // Allow arrow keys to be handled by the command menu for navigation + const commandList = e.currentTarget + .closest('.inline-content') + ?.nextElementSibling?.querySelector('[cmdk-list]'); + + // Create a synthetic keyboard event for the command menu + const syntheticEvent = new KeyboardEvent('keydown', { + key: e.key, + bubbles: true, + cancelable: true, + }); + commandList?.dispatchEvent(syntheticEvent); + e.preventDefault(); + } else if (e.key === 'Enter') { + // Handle Enter key to select the currently highlighted item + const selectedItem = e.currentTarget + .closest('.inline-content') + ?.nextElementSibling?.querySelector( + '[cmdk-item][data-selected="true"]', + ) as HTMLElement; + + selectedItem?.click(); + e.preventDefault(); + } + }; + return ( { - if ( - (e.key === 'Backspace' && search.length === 0) || - e.key === 'Escape' - ) { - e.preventDefault(); - - updateInlineContent({ - type: 'interlinkingSearchInline', - props: { - disabled: true, - trigger, - }, - }); - - contentRef(null); - editor.focus(); - editor.insertInlineContent(['']); - } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { - // Allow arrow keys to be handled by the command menu for navigation - const commandList = e.currentTarget - .closest('.inline-content') - ?.nextElementSibling?.querySelector('[cmdk-list]'); - - // Create a synthetic keyboard event for the command menu - const syntheticEvent = new KeyboardEvent('keydown', { - key: e.key, - bubbles: true, - cancelable: true, - }); - commandList?.dispatchEvent(syntheticEvent); - e.preventDefault(); - } else if (e.key === 'Enter') { - // Handle Enter key to select the currently highlighted item - const selectedItem = e.currentTarget - .closest('.inline-content') - ?.nextElementSibling?.querySelector( - '[cmdk-item][data-selected="true"]', - ) as HTMLElement; - - selectedItem?.click(); - e.preventDefault(); - } - }} + onKeyDown={handleKeyDown} />