mirror of
https://github.com/dream-num/univer.git
synced 2026-09-24 15:32:18 +08:00
fix(docs): quick insert (#4885)
This commit is contained in:
+15
-3
@@ -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 },
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user