Skip to content

Commit cdb26b4

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 23a0f27 commit cdb26b4

File tree

3 files changed

+60
-44
lines changed

3 files changed

+60
-44
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to
1111
- ♿(frontend) improve accessibility:
1212
- ♿(frontend) improve ARIA in doc grid and editor for a11y #1519
1313
- 🐛(docx) fix image overflow by limiting width to 600px during export #1525
14+
- 🐛(frontend) preserve @ character when esc is pressed after typing it #1512
1415

1516
## [3.9.0] - 2025-11-10
1617

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,12 @@ test.describe('Doc Editor', () => {
806806
});
807807
await expect(interlinkChild1).toBeVisible({ timeout: 10000 });
808808
await expect(interlinkChild1.locator('svg').first()).toBeVisible();
809+
810+
await page.keyboard.press('@');
811+
812+
await page.keyboard.press('Escape');
813+
814+
await expect(editor.getByText('@')).toBeVisible();
809815
});
810816

811817
test('it checks multiple big doc scroll to the top', async ({

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';
@@ -99,6 +100,55 @@ export const SearchPage = ({
99100
}, 100);
100101
}, [inputRef]);
101102

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

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

0 commit comments

Comments
 (0)