mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 18:09:58 +08:00
fix(editor): slash menu on mobile browser (#14328)
## Summary Fixed the slash menu not appearing when typing `/` on mobile web browsers. ## Problem Mobile browsers don't reliably fire keyboard events (`keyDown`) when using virtual keyboards. This caused the slash menu trigger to fail on mobile devices. ## Solution - Changed from handling `keyDown` events to `beforeInput` events - `InputEvent` is fired consistently across all platforms (mobile and desktop) - Added proper handling for IME composition to avoid duplicate triggers - Uses `waitForUpdate()` to ensure the input is processed before checking for the trigger ## Test plan - [x] Tested on mobile Safari (iOS) - [x] Tested on mobile Chrome (Android) - [x] Verified desktop browsers still work correctly - [x] Verified IME input (e.g., Chinese/Japanese) doesn't trigger false positives Fixes #12910 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved slash menu input handling for better reliability and enhanced IME (input method editor) composition support. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
committed by
GitHub
parent
b778207af9
commit
0f0bfb9f06
@@ -1,8 +1,11 @@
|
|||||||
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
|
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
|
||||||
import type { AffineInlineEditor } from '@blocksuite/affine-shared/types';
|
import type { AffineInlineEditor } from '@blocksuite/affine-shared/types';
|
||||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||||
import type { UIEventStateContext } from '@blocksuite/std';
|
import {
|
||||||
import { TextSelection, WidgetComponent } from '@blocksuite/std';
|
TextSelection,
|
||||||
|
type UIEventStateContext,
|
||||||
|
WidgetComponent,
|
||||||
|
} from '@blocksuite/std';
|
||||||
import { InlineEditor } from '@blocksuite/std/inline';
|
import { InlineEditor } from '@blocksuite/std/inline';
|
||||||
import debounce from 'lodash-es/debounce';
|
import debounce from 'lodash-es/debounce';
|
||||||
|
|
||||||
@@ -59,9 +62,7 @@ const showSlashMenu = debounce(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export class AffineSlashMenuWidget extends WidgetComponent {
|
export class AffineSlashMenuWidget extends WidgetComponent {
|
||||||
private readonly _getInlineEditor = (
|
private readonly _getInlineEditor = (evt: CompositionEvent | InputEvent) => {
|
||||||
evt: KeyboardEvent | CompositionEvent
|
|
||||||
) => {
|
|
||||||
if (evt.target instanceof HTMLElement) {
|
if (evt.target instanceof HTMLElement) {
|
||||||
const editor = (
|
const editor = (
|
||||||
evt.target.closest('.inline-editor') as {
|
evt.target.closest('.inline-editor') as {
|
||||||
@@ -152,18 +153,27 @@ export class AffineSlashMenuWidget extends WidgetComponent {
|
|||||||
this._handleInput(inlineEditor, true);
|
this._handleInput(inlineEditor, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _onKeyDown = (ctx: UIEventStateContext) => {
|
private readonly _onBeforeInput = (ctx: UIEventStateContext) => {
|
||||||
const eventState = ctx.get('keyboardState');
|
const event = ctx.get('defaultState').event;
|
||||||
const event = eventState.raw;
|
if (!(event instanceof InputEvent)) return;
|
||||||
|
|
||||||
const key = event.key;
|
// Skip non-character inputs and IME composition (handled by _onCompositionEnd)
|
||||||
|
if (event.data === null || event.isComposing) return;
|
||||||
|
|
||||||
if (event.isComposing || key !== AFFINE_SLASH_MENU_TRIGGER_KEY) return;
|
// Quick check: only proceed if the input contains the trigger key
|
||||||
|
if (!event.data.includes(AFFINE_SLASH_MENU_TRIGGER_KEY)) return;
|
||||||
|
|
||||||
const inlineEditor = this._getInlineEditor(event);
|
const inlineEditor = this._getInlineEditor(event);
|
||||||
if (!inlineEditor) return;
|
if (!inlineEditor) return;
|
||||||
|
|
||||||
this._handleInput(inlineEditor, false);
|
// Wait for the input to be processed, then handle it
|
||||||
|
// Pass true because after waitForUpdate(), the range is already synced
|
||||||
|
inlineEditor
|
||||||
|
.waitForUpdate()
|
||||||
|
.then(() => {
|
||||||
|
this._handleInput(inlineEditor, true);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
};
|
};
|
||||||
|
|
||||||
get config() {
|
get config() {
|
||||||
@@ -177,8 +187,7 @@ export class AffineSlashMenuWidget extends WidgetComponent {
|
|||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
// this.handleEvent('beforeInput', this._onBeforeInput);
|
this.handleEvent('beforeInput', this._onBeforeInput);
|
||||||
this.handleEvent('keyDown', this._onKeyDown);
|
|
||||||
this.handleEvent('compositionEnd', this._onCompositionEnd);
|
this.handleEvent('compositionEnd', this._onCompositionEnd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user