mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-06 08:50:50 +08:00
fix(core): cannot input space at the beginning of a blank paragraph (#12166)
### TL:DR fix: cannot input space at the beginning of a blank paragraph > CLOSE BS-3427 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved space key handling in the editor: pressing space on an empty AI input now hides the AI panel and inserts a space character back into the editor. - **Bug Fixes** - Prevented the AI panel from processing empty input when space is pressed, ensuring smoother user experience. - **Tests** - Added an end-to-end test verifying that pressing space on an empty AI input hides the AI panel and inserts a space. - **Refactor** - Streamlined event handling logic for space key detection in the editor. - **Chores** - Enhanced editor content retrieval to optionally preserve whitespace while removing invisible characters. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,9 +1,34 @@
|
|||||||
import { TextSelection } from '@blocksuite/affine/std';
|
import type { RichText } from '@blocksuite/affine/rich-text';
|
||||||
|
import { type EditorHost, TextSelection } from '@blocksuite/affine/std';
|
||||||
|
|
||||||
import { handleInlineAskAIAction } from '../../actions/doc-handler';
|
import { handleInlineAskAIAction } from '../../actions/doc-handler';
|
||||||
import { AIProvider } from '../../provider';
|
import { AIProvider } from '../../provider';
|
||||||
import type { AffineAIPanelWidget } from '../../widgets/ai-panel/ai-panel';
|
import type { AffineAIPanelWidget } from '../../widgets/ai-panel/ai-panel';
|
||||||
|
|
||||||
|
function isSpaceEvent(event: KeyboardEvent) {
|
||||||
|
return event.key === ' ' && event.which === 32 && !event.isComposing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertSpace(host: EditorHost) {
|
||||||
|
const textSelection = host.selection.find(TextSelection);
|
||||||
|
if (!textSelection || !textSelection.isCollapsed()) return;
|
||||||
|
|
||||||
|
const blockComponent = host.view.getBlock(textSelection.from.blockId);
|
||||||
|
if (!blockComponent) return;
|
||||||
|
|
||||||
|
const richText = blockComponent.querySelector('rich-text') as RichText | null;
|
||||||
|
if (!richText) return;
|
||||||
|
|
||||||
|
const inlineEditor = richText.inlineEditor;
|
||||||
|
inlineEditor?.insertText(
|
||||||
|
{
|
||||||
|
index: textSelection.from.index,
|
||||||
|
length: 0,
|
||||||
|
},
|
||||||
|
' '
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function setupSpaceAIEntry(panel: AffineAIPanelWidget) {
|
export function setupSpaceAIEntry(panel: AffineAIPanelWidget) {
|
||||||
// Background: The keydown event triggered by a space may originate from:
|
// Background: The keydown event triggered by a space may originate from:
|
||||||
// 1. Normal space insertion
|
// 1. Normal space insertion
|
||||||
@@ -18,12 +43,19 @@ export function setupSpaceAIEntry(panel: AffineAIPanelWidget) {
|
|||||||
const host = panel.host;
|
const host = panel.host;
|
||||||
const keyboardState = ctx.get('keyboardState');
|
const keyboardState = ctx.get('keyboardState');
|
||||||
const event = keyboardState.raw;
|
const event = keyboardState.raw;
|
||||||
if (
|
if (AIProvider.actions.chat && isSpaceEvent(event)) {
|
||||||
AIProvider.actions.chat &&
|
// If the AI panel is in the input state and the input content is empty,
|
||||||
event.key === ' ' &&
|
// insert a space back into the editor.
|
||||||
event.which === 32 &&
|
if (panel.state === 'input') {
|
||||||
!event.isComposing
|
const input = panel.shadowRoot?.querySelector('ai-panel-input');
|
||||||
) {
|
if (input?.textarea.value.trim() === '') {
|
||||||
|
event.preventDefault();
|
||||||
|
insertSpace(host);
|
||||||
|
panel.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -35,4 +35,20 @@ test.describe('AIBasic/Guidance', () => {
|
|||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
await expect(page.locator('affine-ai-panel-widget')).not.toBeVisible();
|
await expect(page.locator('affine-ai-panel-widget')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should hide AI panel and insert space back to editor when space is pressed on empty input', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.focusToEditor(page);
|
||||||
|
await page.keyboard.press('Space');
|
||||||
|
await expect(page.locator('affine-ai-panel-widget')).toBeVisible();
|
||||||
|
|
||||||
|
await page.keyboard.press('Space');
|
||||||
|
await expect(page.locator('affine-ai-panel-widget')).not.toBeVisible();
|
||||||
|
await expect(async () => {
|
||||||
|
const content = await utils.editor.getEditorContent(page, false);
|
||||||
|
expect(content).toBe(' ');
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,14 +21,20 @@ export class EditorUtils {
|
|||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getEditorContent(page: Page) {
|
public static async getEditorContent(page: Page, trim = true) {
|
||||||
let content = '';
|
let content = '';
|
||||||
let retry = 3;
|
let retry = 3;
|
||||||
while (!content && retry > 0) {
|
while (!content && retry > 0) {
|
||||||
const lines = await page.$$('page-editor .inline-editor');
|
const lines = await page.$$('page-editor .inline-editor');
|
||||||
const contents = await Promise.all(lines.map(el => el.innerText()));
|
const contents = await Promise.all(lines.map(el => el.innerText()));
|
||||||
content = contents
|
content = contents
|
||||||
.map(c => c.replace(/[\u200B-\u200D\uFEFF]/g, '').trim())
|
.map(c => {
|
||||||
|
const invisibleFiltered = c.replace(/[\u200B-\u200D\uFEFF]/g, '');
|
||||||
|
if (trim) {
|
||||||
|
return invisibleFiltered.trim();
|
||||||
|
}
|
||||||
|
return invisibleFiltered;
|
||||||
|
})
|
||||||
.filter(c => !!c)
|
.filter(c => !!c)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
if (!content) {
|
if (!content) {
|
||||||
|
|||||||
Reference in New Issue
Block a user