feat: doc quick insert placeholder (#4841)

This commit is contained in:
siam
2025-03-17 15:45:47 +08:00
committed by GitHub
parent c47d5254f8
commit 263692c6a3
15 changed files with 144 additions and 8 deletions
@@ -70,6 +70,7 @@
"@univerjs/design": "workspace:*",
"@univerjs/docs": "workspace:*",
"@univerjs/docs-drawing": "workspace:*",
"@univerjs/docs-drawing-ui": "workspace:*",
"@univerjs/docs-ui": "workspace:*",
"@univerjs/drawing": "workspace:*",
"@univerjs/drawing-ui": "workspace:*",
@@ -23,6 +23,7 @@ import { of } from 'rxjs';
import { DeleteSearchKeyCommand } from '../commands/commands/doc-quick-insert.command';
import { CloseQuickInsertPopupOperation, ShowQuickInsertPopupOperation } from '../commands/operations/quick-insert-popup.operation';
import { DocQuickInsertPopupService } from '../services/doc-quick-insert-popup.service';
import { KeywordInputPlaceholder } from '../views/KeywordInputPlaceholder';
import { QuickInsertPopup } from '../views/QuickInsertPopup';
import { builtInMenus } from './built-in-menus';
@@ -52,6 +53,7 @@ export class DocQuickInsertUIController extends Disposable {
private _initComponents() {
this.disposeWithMe(this._componentManager.register(QuickInsertPopup.componentKey, QuickInsertPopup));
this.disposeWithMe(this._componentManager.register(KeywordInputPlaceholder.componentKey, KeywordInputPlaceholder));
this.disposeWithMe(this._componentManager.register('DividerSingle', DividerSingle));
this.disposeWithMe(this._componentManager.register('TextSingle', TextSingle));
@@ -27,6 +27,8 @@ const locale: typeof zhCN = {
group: {
basics: 'Basics',
},
placeholder: 'No results found',
keywordInputPlaceholder: 'Enter keywords',
},
};
@@ -27,6 +27,8 @@ const locale: typeof zhCN = {
group: {
basics: 'بنیادی',
},
placeholder: 'هیچ نتیجهای یافت نشد',
keywordInputPlaceholder: 'وارد کلمات کلیدی',
},
};
@@ -27,6 +27,8 @@ const locale: typeof enUS = {
group: {
basics: 'Basiques',
},
placeholder: 'Aucun résultat',
keywordInputPlaceholder: 'Entrez des mots clés',
},
};
@@ -27,6 +27,8 @@ const locale: typeof zhCN = {
group: {
basics: 'Основные',
},
placeholder: 'Нет результатов',
keywordInputPlaceholder: 'Введите ключевые слова',
},
};
@@ -27,6 +27,8 @@ const locale: typeof zhCN = {
group: {
basics: 'Cơ bản',
},
placeholder: 'Không có kết quả',
keywordInputPlaceholder: 'Nhập từ khóa',
},
};
@@ -25,6 +25,8 @@ const locale = {
group: {
basics: '基础',
},
placeholder: '暂无结果',
keywordInputPlaceholder: '输入关键词',
},
};
@@ -27,6 +27,8 @@ const locale: typeof zhCN = {
group: {
basics: '基礎',
},
placeholder: '無結果',
keywordInputPlaceholder: '輸入關鍵詞',
},
};
@@ -18,9 +18,11 @@ 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 { DocFloatDomController } from '@univerjs/docs-drawing-ui';
import { DocCanvasPopManagerService } from '@univerjs/docs-ui';
import { BehaviorSubject, distinctUntilChanged, map } from 'rxjs';
import { BehaviorSubject, combineLatest, distinctUntilChanged, map, tap } from 'rxjs';
import { DeleteSearchKeyCommand } from '../commands/commands/doc-quick-insert.command';
import { KeywordInputPlaceholder } from '../views/KeywordInputPlaceholder';
import { QuickInsertPopup } from '../views/QuickInsertPopup';
export interface IDocPopupGroupItem {
@@ -42,7 +44,7 @@ export type DocPopupMenu = IDocPopupGroupItem | IDocPopupMenuItem;
export interface IDocPopup {
keyword: string;
menus$: Observable<DocPopupMenu[]>;
placeholder?: React.ReactNode;
Placeholder?: React.ComponentType;
preconditions?: (params: IInsertCommandParams) => boolean;
}
@@ -75,10 +77,16 @@ export class DocQuickInsertPopupService extends Disposable {
private _menuSelectedCallbacks: Set<(menu: IDocPopupMenuItem) => void> = new Set();
private _inputPlaceholderRenderRoot: {
unmount?: IDisposable;
mount: () => void;
} | null = null;
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(DocFloatDomController) private readonly _docFloatDomController: DocFloatDomController
) {
super();
@@ -94,6 +102,21 @@ export class DocQuickInsertPopupService extends Disposable {
}),
distinctUntilChanged()
);
this.disposeWithMe(combineLatest([
this.filterKeyword$.pipe(tap((filterKeyword) => {
if (filterKeyword.length > 0) {
this._inputPlaceholderRenderRoot?.unmount?.dispose();
} else {
this._inputPlaceholderRenderRoot?.mount();
}
})),
this.editPopup$.pipe(tap((popup) => {
if (!popup) {
this._inputPlaceholderRenderRoot?.unmount?.dispose();
}
})),
]).subscribe());
}
resolvePopup(keyword: string) {
@@ -108,6 +131,19 @@ export class DocQuickInsertPopupService extends Disposable {
};
}
private _createInputPlaceholderRenderRoot(mount: () => IDisposable) {
const renderRoot: {
unmount?: IDisposable;
mount: () => void;
} = {
mount() {
this.unmount = mount();
},
};
return renderRoot;
}
showPopup(options: { popup: IDocPopup; index: number; unitId: string }) {
const { popup, index, unitId } = options;
@@ -125,6 +161,23 @@ export class DocQuickInsertPopupService extends Disposable {
unitId
);
this._inputPlaceholderRenderRoot = this._createInputPlaceholderRenderRoot(() => {
const disposable = this._docCanvasPopupManagerService.attachPopupToRange(
{ startOffset: index + 1, endOffset: index + 1, collapsed: false },
{
componentKey: KeywordInputPlaceholder.componentKey,
onClickOutside: () => {
disposable.dispose();
},
direction: 'horizontal',
},
unitId
);
return disposable;
});
this._inputPlaceholderRenderRoot.mount();
this._editPopup$.next({ disposable, popup, anchor: index, unitId });
}
@@ -0,0 +1,28 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LocaleService } from '@univerjs/core';
import { useDependency } from '@univerjs/ui';
const KeywordInputPlaceholder = () => {
const localeService = useDependency(LocaleService);
// TODO: 需要根据fontSize来计算高度跟偏移
return <div className="univer-translate-y-1.5 univer-text-sm univer-text-gray-500">{localeService.t('docQuickInsert.keywordInputPlaceholder')}</div>;
};
KeywordInputPlaceholder.componentKey = 'KeywordInputPlaceholder';
export { KeywordInputPlaceholder };
@@ -0,0 +1,32 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LocaleService } from '@univerjs/core';
import { useDependency } from '@univerjs/ui';
export const QuickInsertPlaceholder = () => {
const localeService = useDependency(LocaleService);
return (
<div
className={`
univer-flex univer-h-full univer-items-center univer-justify-center univer-rounded-lg univer-bg-white
univer-px-12 univer-py-6 univer-text-gray-400
`}
>
<span>{localeService.t('docQuickInsert.placeholder')}</span>
</div>
);
};
@@ -21,6 +21,7 @@ import { ComponentManager, IShortcutService, KeyCode, useDependency, useObservab
import { useEffect, useMemo, useRef, useState } from 'react';
import { CloseQuickInsertPopupOperation } from '../commands/operations/quick-insert-popup.operation';
import { DocQuickInsertPopupService } from '../services/doc-quick-insert-popup.service';
import { QuickInsertPlaceholder } from './QuickInsertPlaceholder';
function filterMenusByKeyword(menus: DocPopupMenu[], keyword: string) {
return menus
@@ -251,6 +252,8 @@ const QuickInsertPopup = () => {
const hasMenus = filteredMenus.length > 0;
const Placeholder = currentPopup?.popup.Placeholder || QuickInsertPlaceholder;
return (
<div
className={clsx(`
@@ -266,7 +269,7 @@ const QuickInsertPopup = () => {
</Menu>
</div>
)
: currentPopup?.popup.placeholder}
: <Placeholder />}
</div>
);
@@ -279,6 +279,7 @@ export class DocCanvasPopManagerService extends Disposable {
const { positions: bounds, positions$: bounds$, disposable } = this._createRangePositionObserver(range, currentRender);
const position$ = bounds$.pipe(map((bounds) => direction.includes('top') ? bounds[0] : bounds[bounds.length - 1]));
const id = this._globalPopupManagerService.addPopup({
...popup,
unitId,
@@ -287,10 +288,9 @@ export class DocCanvasPopManagerService extends Disposable {
anchorRect$: position$,
excludeRects: bounds,
excludeRects$: bounds$,
direction:
(direction.includes('top') || direction.includes('bottom')) ?
bounds.length > 1 ? multipleDirection ?? direction : direction
: 'bottom',
direction: (['top', 'bottom', 'horizontal'].includes(direction)) ?
bounds.length > 1 ? multipleDirection ?? direction : direction
: 'bottom',
canvasElement: currentRender.engine.getCanvasElement(),
});
+3
View File
@@ -1459,6 +1459,9 @@ importers:
'@univerjs/docs-drawing':
specifier: workspace:*
version: link:../docs-drawing
'@univerjs/docs-drawing-ui':
specifier: workspace:*
version: link:../docs-drawing-ui
'@univerjs/docs-ui':
specifier: workspace:*
version: link:../docs-ui