Skip to content

Commit 044ab2f

Browse files
committed
✨(frontend) preserve @ character when esc is pressed after typing it
improves user experience by keeping @ symbol after cancelling mention trigger Signed-off-by: Cyril <c.gromoff@gmail.com>
1 parent 145c688 commit 044ab2f

File tree

3 files changed

+77
-44
lines changed

3 files changed

+77
-44
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to
1515

1616
- 🐛(frontend) fix duplicate document entries in grid #1479
1717
- 🐛(frontend) show full nested doc names with ajustable bar #1456
18+
- 🐛(frontend) preserve @ character when esc is pressed after typing it #1512
1819

1920
## [3.8.2] - 2025-10-17
2021

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,29 @@ test.describe('Doc Editor', () => {
784784
).toBeVisible();
785785
});
786786

787+
test('it keeps @ when pressing Escape', async ({ page, browserName }) => {
788+
const [randomDoc] = await createDoc(
789+
page,
790+
'doc-interlink-esc',
791+
browserName,
792+
1,
793+
);
794+
795+
await verifyDocName(page, randomDoc);
796+
797+
const editor = await getEditor({ page });
798+
await page.keyboard.press('@');
799+
800+
const searchInput = page.locator(
801+
"span[data-inline-content-type='interlinkingSearchInline'] input",
802+
);
803+
await expect(searchInput).toBeVisible();
804+
805+
await page.keyboard.press('Escape');
806+
807+
await expect(editor.getByText('@')).toBeVisible();
808+
});
809+
787810
test('it checks multiple big doc scroll to the top', async ({
788811
page,
789812
browserName,

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
StyleSchema,
44
} from '@blocknote/core';
55
import { useBlockNoteEditor } from '@blocknote/react';
6+
import type { KeyboardEvent } from 'react';
67
import { useEffect, useRef, useState } from 'react';
78
import { useTranslation } from 'react-i18next';
89
import { css } from 'styled-components';
@@ -98,6 +99,55 @@ export const SearchPage = ({
9899
}, 100);
99100
}, [inputRef]);
100101

102+
const closeSearch = (insertContent: string) => {
103+
updateInlineContent({
104+
type: 'interlinkingSearchInline',
105+
props: {
106+
disabled: true,
107+
trigger,
108+
},
109+
});
110+
111+
contentRef(null);
112+
editor.focus();
113+
editor.insertInlineContent([insertContent]);
114+
};
115+
116+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
117+
if (e.key === 'Escape') {
118+
e.preventDefault();
119+
// Keep the trigger character ('@' or '/') in the editor when closing with Escape
120+
closeSearch(trigger);
121+
} else if (e.key === 'Backspace' && search.length === 0) {
122+
e.preventDefault();
123+
closeSearch('');
124+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
125+
// Allow arrow keys to be handled by the command menu for navigation
126+
const commandList = e.currentTarget
127+
.closest('.inline-content')
128+
?.nextElementSibling?.querySelector('[cmdk-list]');
129+
130+
// Create a synthetic keyboard event for the command menu
131+
const syntheticEvent = new KeyboardEvent('keydown', {
132+
key: e.key,
133+
bubbles: true,
134+
cancelable: true,
135+
});
136+
commandList?.dispatchEvent(syntheticEvent);
137+
e.preventDefault();
138+
} else if (e.key === 'Enter') {
139+
// Handle Enter key to select the currently highlighted item
140+
const selectedItem = e.currentTarget
141+
.closest('.inline-content')
142+
?.nextElementSibling?.querySelector(
143+
'[cmdk-item][data-selected="true"]',
144+
) as HTMLElement;
145+
146+
selectedItem?.click();
147+
e.preventDefault();
148+
}
149+
};
150+
101151
return (
102152
<Box as="span" $position="relative">
103153
<Box
@@ -123,50 +173,7 @@ export const SearchPage = ({
123173
const value = (e.target as HTMLInputElement).value;
124174
setSearch(value);
125175
}}
126-
onKeyDown={(e) => {
127-
if (
128-
(e.key === 'Backspace' && search.length === 0) ||
129-
e.key === 'Escape'
130-
) {
131-
e.preventDefault();
132-
133-
updateInlineContent({
134-
type: 'interlinkingSearchInline',
135-
props: {
136-
disabled: true,
137-
trigger,
138-
},
139-
});
140-
141-
contentRef(null);
142-
editor.focus();
143-
editor.insertInlineContent(['']);
144-
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
145-
// Allow arrow keys to be handled by the command menu for navigation
146-
const commandList = e.currentTarget
147-
.closest('.inline-content')
148-
?.nextElementSibling?.querySelector('[cmdk-list]');
149-
150-
// Create a synthetic keyboard event for the command menu
151-
const syntheticEvent = new KeyboardEvent('keydown', {
152-
key: e.key,
153-
bubbles: true,
154-
cancelable: true,
155-
});
156-
commandList?.dispatchEvent(syntheticEvent);
157-
e.preventDefault();
158-
} else if (e.key === 'Enter') {
159-
// Handle Enter key to select the currently highlighted item
160-
const selectedItem = e.currentTarget
161-
.closest('.inline-content')
162-
?.nextElementSibling?.querySelector(
163-
'[cmdk-item][data-selected="true"]',
164-
) as HTMLElement;
165-
166-
selectedItem?.click();
167-
e.preventDefault();
168-
}
169-
}}
176+
onKeyDown={handleKeyDown}
170177
/>
171178
</Box>
172179
<Box
@@ -223,6 +230,8 @@ export const SearchPage = ({
223230
},
224231
});
225232

233+
contentRef(null);
234+
226235
editor.insertInlineContent([
227236
{
228237
type: 'interlinkingLinkInline',

0 commit comments

Comments
 (0)