mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
refactor(core): add keyPress event to fix IME space detection (#11700)
### TL;DR Refactor space-triggered AI Widget activation logic from `keydown` to `keypress` event listeners ### Background The `keydown` event triggered by a space may originate from: 1. Normal space insertion 2. Space triggered by input method confirming candidate words In scenarios like (2), some browsers (see [ISSUE](https://github.com/toeverything/AFFiNE/issues/11541)) and input method callbacks produce events identical to scenario (1),making it impossible to distinguish between the two. To fix this, the space-activated AI listener uses the `keypress` event: In scenario 2, `event.which !== 32` (may be `30430` or other values) can be used to differentiate from scenario 1. > CLOSE BS-3081
This commit is contained in:
@@ -59,6 +59,21 @@ export class KeyboardControl {
|
|||||||
|
|
||||||
private composition = false;
|
private composition = false;
|
||||||
|
|
||||||
|
private readonly _press = (event: KeyboardEvent) => {
|
||||||
|
if (!this._shouldTrigger(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const keyboardEventState = new KeyboardEventState({
|
||||||
|
event,
|
||||||
|
composing: this.composition,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._dispatcher.run(
|
||||||
|
'keyPress',
|
||||||
|
this._createContext(event, keyboardEventState)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
constructor(private readonly _dispatcher: UIEventDispatcher) {}
|
constructor(private readonly _dispatcher: UIEventDispatcher) {}
|
||||||
|
|
||||||
private _createContext(event: Event, keyboardState: KeyboardEventState) {
|
private _createContext(event: Event, keyboardState: KeyboardEventState) {
|
||||||
@@ -105,6 +120,11 @@ export class KeyboardControl {
|
|||||||
listen() {
|
listen() {
|
||||||
this._dispatcher.disposables.addFromEvent(document, 'keydown', this._down);
|
this._dispatcher.disposables.addFromEvent(document, 'keydown', this._down);
|
||||||
this._dispatcher.disposables.addFromEvent(document, 'keyup', this._up);
|
this._dispatcher.disposables.addFromEvent(document, 'keyup', this._up);
|
||||||
|
this._dispatcher.disposables.addFromEvent(
|
||||||
|
document,
|
||||||
|
'keypress',
|
||||||
|
this._press
|
||||||
|
);
|
||||||
this._dispatcher.disposables.addFromEvent(
|
this._dispatcher.disposables.addFromEvent(
|
||||||
document,
|
document,
|
||||||
'compositionstart',
|
'compositionstart',
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const eventNames = [
|
|||||||
|
|
||||||
'keyDown',
|
'keyDown',
|
||||||
'keyUp',
|
'keyUp',
|
||||||
|
'keyPress',
|
||||||
|
|
||||||
'selectionChange',
|
'selectionChange',
|
||||||
'compositionStart',
|
'compositionStart',
|
||||||
|
|||||||
@@ -5,20 +5,31 @@ import { AIProvider } from '../../provider';
|
|||||||
import type { AffineAIPanelWidget } from '../../widgets/ai-panel/ai-panel';
|
import type { AffineAIPanelWidget } from '../../widgets/ai-panel/ai-panel';
|
||||||
|
|
||||||
export function setupSpaceAIEntry(panel: AffineAIPanelWidget) {
|
export function setupSpaceAIEntry(panel: AffineAIPanelWidget) {
|
||||||
panel.handleEvent('keyDown', ctx => {
|
// Background: The keydown event triggered by a space may originate from:
|
||||||
|
// 1. Normal space insertion
|
||||||
|
// 2. Space triggered by input method confirming candidate words
|
||||||
|
// In scenarios like (2), some browsers (see [ISSUE](https://github.com/toeverything/AFFiNE/issues/11541))
|
||||||
|
// and input method callbacks produce events identical to scenario (1),
|
||||||
|
// making it impossible to distinguish between the two.
|
||||||
|
//
|
||||||
|
// To fix this, the space-activated AI listener uses the `keypress` event:
|
||||||
|
// In scenario 2, `event.which !== 32` (may be `30430` or other values) can be used to differentiate from scenario 1.
|
||||||
|
panel.handleEvent('keyPress', ctx => {
|
||||||
const host = panel.host;
|
const host = panel.host;
|
||||||
const keyboardState = ctx.get('keyboardState');
|
const keyboardState = ctx.get('keyboardState');
|
||||||
|
const event = keyboardState.raw;
|
||||||
if (
|
if (
|
||||||
AIProvider.actions.chat &&
|
AIProvider.actions.chat &&
|
||||||
keyboardState.raw.key === ' ' &&
|
event.key === ' ' &&
|
||||||
!keyboardState.raw.isComposing
|
event.which === 32 &&
|
||||||
|
!event.isComposing
|
||||||
) {
|
) {
|
||||||
const selection = host.selection.find(TextSelection);
|
const selection = host.selection.find(TextSelection);
|
||||||
if (selection && selection.isCollapsed() && selection.from.index === 0) {
|
if (selection && selection.isCollapsed() && selection.from.index === 0) {
|
||||||
const block = host.view.getBlock(selection.blockId);
|
const block = host.view.getBlock(selection.blockId);
|
||||||
if (!block?.model?.text || block.model.text?.length > 0) return;
|
if (!block?.model?.text || block.model.text?.length > 0) return;
|
||||||
|
|
||||||
keyboardState.raw.preventDefault();
|
event.preventDefault();
|
||||||
handleInlineAskAIAction(host);
|
handleInlineAskAIAction(host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIBasic/Guidance', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show AI panel when space is pressed on empty paragraph', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.focusToEditor(page);
|
||||||
|
await page.keyboard.press('Space');
|
||||||
|
await expect(page.locator('affine-ai-panel-widget')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user