fix(editor): repeat trigger keys of at-menu was added (#11631)

Close [BS-2716](https://linear.app/affine-design/issue/BS-2716/移动端通过toolpanel唤起的at-menu,出现两个)
This commit is contained in:
L-Sun
2025-04-13 06:46:37 +00:00
parent 26d2ed8afb
commit 076c5ba044
6 changed files with 61 additions and 71 deletions

View File

@@ -1,8 +1,5 @@
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model'; import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
import { import { insertContent } from '@blocksuite/affine-rich-text';
getInlineEditorByModel,
insertContent,
} from '@blocksuite/affine-rich-text';
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts'; import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import { createDefaultDoc } from '@blocksuite/affine-shared/utils'; import { createDefaultDoc } from '@blocksuite/affine-shared/utils';
import { import {
@@ -68,22 +65,7 @@ const linkedDocSlashMenuConfig: SlashMenuConfig = {
if (!linkedDocWidget) return; if (!linkedDocWidget) return;
// TODO(@L-Sun): make linked-doc-widget as extension // TODO(@L-Sun): make linked-doc-widget as extension
// @ts-expect-error same as above // @ts-expect-error same as above
const triggerKey = linkedDocWidget.config.triggerKeys[0];
insertContent(std, model, triggerKey);
const inlineEditor = getInlineEditorByModel(std, model);
if (inlineEditor) {
// Wait for range to be updated
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(
() => {
// TODO(@L-Sun): make linked-doc-widget as extension
subscription.unsubscribe();
// @ts-expect-error same as above
linkedDocWidget.show({ addTriggerKey: true }); linkedDocWidget.show({ addTriggerKey: true });
}
);
}
}, },
}, },
], ],

View File

@@ -34,10 +34,7 @@ import {
toggleUnderline, toggleUnderline,
} from '@blocksuite/affine-inline-preset'; } from '@blocksuite/affine-inline-preset';
import type { FrameBlockModel } from '@blocksuite/affine-model'; import type { FrameBlockModel } from '@blocksuite/affine-model';
import { import { insertContent } from '@blocksuite/affine-rich-text';
getInlineEditorByModel,
insertContent,
} from '@blocksuite/affine-rich-text';
import { import {
copySelectedModelsCommand, copySelectedModelsCommand,
deleteSelectedModelsCommand, deleteSelectedModelsCommand,
@@ -348,35 +345,11 @@ const pageToolGroup: KeyboardToolPanelGroup = {
); );
if (!linkedDocWidget) return; if (!linkedDocWidget) return;
assertType<AffineLinkedDocWidget>(linkedDocWidget); assertType<AffineLinkedDocWidget>(linkedDocWidget);
const triggerKey = linkedDocWidget.config.triggerKeys[0];
std.command
.chain()
.pipe(getSelectedModelsCommand)
.pipe(ctx => {
const { selectedModels } = ctx;
if (!selectedModels?.length) return;
const currentModel = selectedModels[0];
insertContent(std, currentModel, triggerKey);
const inlineEditor = getInlineEditorByModel(std, currentModel);
// Wait for range to be updated
if (inlineEditor) {
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(
() => {
subscription.unsubscribe();
linkedDocWidget.show({ linkedDocWidget.show({
mode: 'mobile', mode: 'mobile',
addTriggerKey: true, addTriggerKey: true,
}); });
closeToolPanel(); closeToolPanel();
}
);
}
})
.run();
}, },
}, },
], ],

View File

@@ -112,6 +112,7 @@ export type LinkedDocContext = {
std: BlockStdScope; std: BlockStdScope;
inlineEditor: AffineInlineEditor; inlineEditor: AffineInlineEditor;
startRange: InlineRange; startRange: InlineRange;
startNativeRange: Range;
triggerKey: string; triggerKey: string;
config: LinkedWidgetConfig; config: LinkedWidgetConfig;
close: () => void; close: () => void;

View File

@@ -7,7 +7,6 @@ import {
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme'; import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
import { import {
createKeydownObserver, createKeydownObserver,
getCurrentNativeRange,
getPopperPosition, getPopperPosition,
getViewportElement, getViewportElement,
} from '@blocksuite/affine-shared/utils'; } from '@blocksuite/affine-shared/utils';
@@ -160,11 +159,15 @@ export class LinkedDocPopover extends SignalWatcher(
// init // init
this._updateLinkedDocGroup().catch(console.error); this._updateLinkedDocGroup().catch(console.error);
this._disposables.addFromEvent(this, 'mousedown', e => { this._disposables.addFromEvent(this, 'pointerdown', e => {
// Prevent input from losing focus // Prevent input from losing focus
e.preventDefault(); e.preventDefault();
}); });
this._disposables.addFromEvent(window, 'mousedown', e => { this._disposables.addFromEvent(this, 'mousedown', e => {
// Prevent input from losing focus in electron
e.preventDefault();
});
this._disposables.addFromEvent(window, 'pointerdown', e => {
if (e.target === this) return; if (e.target === this) return;
// We don't clear the query when clicking outside the popover // We don't clear the query when clicking outside the popover
this.context.close(); this.context.close();
@@ -338,11 +341,8 @@ export class LinkedDocPopover extends SignalWatcher(
override willUpdate() { override willUpdate() {
if (!this.hasUpdated) { if (!this.hasUpdated) {
const curRange = getCurrentNativeRange();
if (!curRange) return;
const updatePosition = throttle(() => { const updatePosition = throttle(() => {
this._position = getPopperPosition(this, curRange); this._position = getPopperPosition(this, this.context.startNativeRange);
}, 10); }, 10);
this.disposables.addFromEvent(window, 'resize', updatePosition); this.disposables.addFromEvent(window, 'resize', updatePosition);

View File

@@ -43,6 +43,19 @@ export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
private readonly _mode$ = signal<'desktop' | 'mobile' | 'none'>('none'); private readonly _mode$ = signal<'desktop' | 'mobile' | 'none'>('none');
private _addTriggerKey(inlineEditor: InlineEditor, triggerKey: string) {
const inlineRange = inlineEditor.getInlineRange();
if (!inlineRange) return;
inlineEditor.insertText(
{ index: inlineRange.index, length: 0 },
triggerKey
);
inlineEditor.setInlineRange({
index: inlineRange.index + triggerKey.length,
length: 0,
});
}
private _updateInputRects() { private _updateInputRects() {
if (!this._context) return; if (!this._context) return;
const { inlineEditor, startRange, triggerKey } = this._context; const { inlineEditor, startRange, triggerKey } = this._context;
@@ -258,27 +271,30 @@ export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
inlineEditor = props.inlineEditor; inlineEditor = props.inlineEditor;
} }
const inlineRange = inlineEditor.getInlineRange();
if (!inlineRange) return;
if (addTriggerKey) { if (addTriggerKey) {
inlineEditor.insertText( this._addTriggerKey(inlineEditor, primaryTriggerKey);
{ index: inlineRange.index, length: 0 }, // we need to wait the range sync to get the correct startNativeRange
primaryTriggerKey const subscription = inlineEditor.slots.inlineRangeSync.subscribe(() => {
); this.show({ ...props, addTriggerKey: false });
inlineEditor.setInlineRange({ subscription.unsubscribe();
index: inlineRange.index + primaryTriggerKey.length,
length: 0,
}); });
return;
} }
const startRange = inlineEditor.getInlineRange();
if (!startRange) return;
const startNativeRange = inlineEditor.getNativeRange();
if (!startNativeRange) return;
const disposable = inlineEditor.slots.renderComplete.subscribe(() => { const disposable = inlineEditor.slots.renderComplete.subscribe(() => {
this._updateInputRects(); this._updateInputRects();
}); });
this._context = { this._context = {
std: this.std, std: this.std,
inlineEditor, inlineEditor,
startRange: inlineRange, startRange,
startNativeRange,
triggerKey: primaryTriggerKey, triggerKey: primaryTriggerKey,
config: this.config, config: this.config,
close: () => { close: () => {

View File

@@ -841,3 +841,21 @@ test('delete block by slash menu should remove children', async ({
await redoByKeyboard(page); await redoByKeyboard(page);
await assertRichTexts(page, ['123']); await assertRichTexts(page, ['123']);
}); });
test('should slash menu can trigger linked doc popover', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyParagraphState(page);
await focusRichText(page);
await type(page, '/linked');
await pressEnter(page);
await expect(page.locator('.linked-doc-popover')).toBeVisible();
await assertRichTexts(page, ['@']);
await type(page, 'doc');
await pressEnter(page);
await expect(page.locator('affine-reference')).toBeVisible();
await expect(
page.locator('affine-reference .affine-reference-title')
).toHaveText('doc');
});