Skip to content

Commit 4682a5d

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 4682a5d

File tree

3 files changed

+76
-44
lines changed

3 files changed

+76
-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: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,55 @@ export const SearchPage = ({
9898
}, 100);
9999
}, [inputRef]);
100100

101+
const closeSearch = (insertContent: string) => {
102+
updateInlineContent({
103+
type: 'interlinkingSearchInline',
104+
props: {
105+
disabled: true,
106+
trigger,
107+
},
108+
});
109+
110+
contentRef(null);
111+
editor.focus();
112+
editor.insertInlineContent([insertContent]);
113+
};
114+
115+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
116+
if (e.key === 'Escape') {
117+
e.preventDefault();
118+
// Keep the trigger character ('@' or '/') in the editor when closing with Escape
119+
closeSearch(trigger);
120+
} else if (e.key === 'Backspace' && search.length === 0) {
121+
e.preventDefault();
122+
closeSearch('');
123+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
124+
// Allow arrow keys to be handled by the command menu for navigation
125+
const commandList = e.currentTarget
126+
.closest('.inline-content')
127+
?.nextElementSibling?.querySelector('[cmdk-list]');
128+
129+
// Create a synthetic keyboard event for the command menu
130+
const syntheticEvent = new KeyboardEvent('keydown', {
131+
key: e.key,
132+
bubbles: true,
133+
cancelable: true,
134+
});
135+
commandList?.dispatchEvent(syntheticEvent);
136+
e.preventDefault();
137+
} else if (e.key === 'Enter') {
138+
// Handle Enter key to select the currently highlighted item
139+
const selectedItem = e.currentTarget
140+
.closest('.inline-content')
141+
?.nextElementSibling?.querySelector(
142+
'[cmdk-item][data-selected="true"]',
143+
) as HTMLElement;
144+
145+
selectedItem?.click();
146+
e.preventDefault();
147+
}
148+
};
149+
101150
return (
102151
<Box as="span" $position="relative">
103152
<Box
@@ -123,50 +172,7 @@ export const SearchPage = ({
123172
const value = (e.target as HTMLInputElement).value;
124173
setSearch(value);
125174
}}
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-
}}
175+
onKeyDown={handleKeyDown}
170176
/>
171177
</Box>
172178
<Box
@@ -223,6 +229,8 @@ export const SearchPage = ({
223229
},
224230
});
225231

232+
contentRef(null);
233+
226234
editor.insertInlineContent([
227235
{
228236
type: 'interlinkingLinkInline',

0 commit comments

Comments
 (0)