fix(docs): quick insert (#4885)

This commit is contained in:
siam
2025-03-27 16:21:09 +08:00
committed by GitHub
parent 1a0b34ba2e
commit b65b2639ab
3 changed files with 79 additions and 7 deletions
@@ -15,10 +15,10 @@
*/
import type { IRichTextEditingMutationParams } from '@univerjs/docs';
import type { IDeleteCommandParams, IInsertCommandParams, IMoveCursorOperationParams } from '@univerjs/docs-ui';
import type { IDeleteCommandParams, IIMEInputCommandParams, IInsertCommandParams, IMoveCursorOperationParams } from '@univerjs/docs-ui';
import { DeleteDirection, Direction, Disposable, ICommandService, Inject } from '@univerjs/core';
import { DocSelectionManagerService, RichTextEditingMutation } from '@univerjs/docs';
import { DeleteCommand, DeleteLeftCommand, InsertCommand, MoveCursorOperation } from '@univerjs/docs-ui';
import { DeleteCommand, DeleteLeftCommand, IMEInputCommand, InsertCommand, MoveCursorOperation } from '@univerjs/docs-ui';
import { IShortcutService, KeyCode } from '@univerjs/ui';
import { CloseQuickInsertPopupOperation, ShowQuickInsertPopupOperation } from '../commands/operations/quick-insert-popup.operation';
import { DocQuickInsertPopupService } from '../services/doc-quick-insert-popup.service';
@@ -44,9 +44,10 @@ export class DocQuickInsertTriggerController extends Disposable {
this._initMenuHandler();
}
// eslint-disable-next-line max-lines-per-function
private _initTrigger() {
this.disposeWithMe(
// eslint-disable-next-line complexity
// eslint-disable-next-line complexity, max-lines-per-function
this._commandService.onCommandExecuted((commandInfo) => {
const { _docQuickInsertPopupService, _textSelectionManagerService, _commandService } = this;
if (commandInfo.id === InsertCommand.id) {
@@ -85,6 +86,17 @@ export class DocQuickInsertTriggerController extends Disposable {
}, 100);
}
if (commandInfo.id === IMEInputCommand.id) {
const params = commandInfo.params as IIMEInputCommandParams;
if (!_docQuickInsertPopupService.isComposing && params.isCompositionStart) {
_docQuickInsertPopupService.setIsComposing(true);
}
if (_docQuickInsertPopupService.isComposing && params.isCompositionEnd) {
_docQuickInsertPopupService.setIsComposing(false);
}
}
if (commandInfo.id === RichTextEditingMutation.id) {
const params = commandInfo.params as IRichTextEditingMutationParams;
if (params.isCompositionEnd) {
@@ -18,7 +18,9 @@ import type { DocumentDataModel, IDisposable, Nullable } from '@univerjs/core';
import type { IInsertCommandParams } from '@univerjs/docs-ui';
import type { Observable } from 'rxjs';
import { Disposable, ICommandService, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { DocSelectionManagerService, DocSkeletonManagerService } from '@univerjs/docs';
import { DocCanvasPopManagerService } from '@univerjs/docs-ui';
import { IRenderManagerService } from '@univerjs/engine-render';
import { BehaviorSubject, combineLatest, distinctUntilChanged, map, tap } from 'rxjs';
import { DeleteSearchKeyCommand } from '../commands/commands/doc-quick-insert.command';
import { KeywordInputPlaceholder } from '../views/KeywordInputPlaceholder';
@@ -47,6 +49,9 @@ export interface IDocPopup {
preconditions?: (params: IInsertCommandParams) => boolean;
}
const noopDisposable = {
dispose: () => {},
};
export class DocQuickInsertPopupService extends Disposable {
private readonly _popups: Set<IDocPopup> = new Set();
@@ -62,6 +67,16 @@ export class DocQuickInsertPopupService extends Disposable {
return this._editPopup$.value;
}
private readonly _isComposing$ = new BehaviorSubject<boolean>(false);
readonly isComposing$ = this._isComposing$.asObservable();
get isComposing() {
return this._isComposing$.value;
}
setIsComposing(isComposing: boolean) {
this._isComposing$.next(isComposing);
}
private readonly _inputOffset$ = new BehaviorSubject<{ start: number; end: number }>({ start: 0, end: 0 });
readonly inputOffset$ = this._inputOffset$.asObservable();
get inputOffset() {
@@ -84,7 +99,9 @@ export class DocQuickInsertPopupService extends Disposable {
constructor(
@Inject(DocCanvasPopManagerService) private readonly _docCanvasPopupManagerService: DocCanvasPopManagerService,
@Inject(IUniverInstanceService) private readonly _univerInstanceService: IUniverInstanceService,
@Inject(ICommandService) private readonly _commandService: ICommandService
@Inject(ICommandService) private readonly _commandService: ICommandService,
@Inject(IRenderManagerService) private readonly _renderManagerService: IRenderManagerService,
@Inject(DocSelectionManagerService) private readonly _docSelectionManagerService: DocSelectionManagerService
) {
super();
@@ -92,13 +109,17 @@ export class DocQuickInsertPopupService extends Disposable {
const getBodySlice = (start: number, end: number) => this._univerInstanceService.getCurrentUnitOfType<DocumentDataModel>(UniverInstanceType.UNIVER_DOC)?.getBody()?.dataStream.slice(start, end);
let lastFilterKeyword = '';
this.filterKeyword$ = this._inputOffset$.pipe(
map((offset) => {
const slice = getBodySlice(offset.start, offset.end);
return slice?.slice(1) ?? '';
}),
distinctUntilChanged()
distinctUntilChanged(),
tap((filterKeyword) => {
lastFilterKeyword = filterKeyword;
})
);
this.disposeWithMe(combineLatest([
@@ -109,9 +130,18 @@ export class DocQuickInsertPopupService extends Disposable {
this._inputPlaceholderRenderRoot?.mount();
}
})),
this.isComposing$.pipe(tap((isComposing) => {
if (isComposing) {
this._inputPlaceholderRenderRoot?.unmount?.dispose();
} else {
// If the last filter keyword is empty, after the composition end, mount the input placeholder render root
lastFilterKeyword.length <= 0 && this._inputPlaceholderRenderRoot?.mount();
}
})),
this.editPopup$.pipe(tap((popup) => {
if (!popup) {
this._inputPlaceholderRenderRoot?.unmount?.dispose();
this._inputPlaceholderRenderRoot = null;
}
})),
]).subscribe());
@@ -131,11 +161,27 @@ export class DocQuickInsertPopupService extends Disposable {
private _createInputPlaceholderRenderRoot(mount: () => IDisposable) {
const renderRoot: {
isMounted: boolean;
unmount?: IDisposable;
mount: () => void;
} = {
isMounted: false,
mount() {
this.unmount = mount();
// Prevent duplicate mounting
if (this.isMounted) {
return;
}
this.isMounted = true;
const unmount = mount();
this.unmount = {
dispose: () => {
unmount.dispose();
this.isMounted = false;
},
};
},
};
@@ -160,6 +206,20 @@ export class DocQuickInsertPopupService extends Disposable {
);
this._inputPlaceholderRenderRoot = this._createInputPlaceholderRenderRoot(() => {
const docSkeletonManagerService = this._renderManagerService.getRenderById(unitId)?.with(DocSkeletonManagerService);
const activeRange = this._docSelectionManagerService.getActiveTextRange();
if (!docSkeletonManagerService || !activeRange) {
return noopDisposable;
}
const skeleton = docSkeletonManagerService.getSkeleton();
const curGlyph = skeleton.findNodeByCharIndex(activeRange.startOffset, activeRange.segmentId, activeRange.segmentPage);
const isEmptyLine = curGlyph?.content === '\r';
// Only show filter keyword placeholder on empty line
if (!isEmptyLine) {
return noopDisposable;
}
const disposable = this._docCanvasPopupManagerService.attachPopupToRange(
{ startOffset: index + 1, endOffset: index + 1, collapsed: false },
{
+1 -1
View File
@@ -80,7 +80,7 @@ export {
export { DeleteCustomBlockCommand, DeleteLeftCommand, DeleteRightCommand, type IDeleteCustomBlockParams, MergeTwoParagraphCommand } from './commands/commands/doc-delete.command';
export { getCursorWhenDelete } from './commands/commands/doc-delete.command';
export { IMEInputCommand } from './commands/commands/ime-input.command';
export { type IIMEInputCommandParams, IMEInputCommand } from './commands/commands/ime-input.command';
export {
ResetInlineFormatTextBackgroundColorCommand,
SetInlineFormatBoldCommand,