mirror of
https://github.com/dream-num/univer.git
synced 2026-09-24 15:32:18 +08:00
feat(docs-quick-insert-ui): implement quick insert menu functionality (#4983)
This commit is contained in:
@@ -22,9 +22,17 @@ import { ConfigContext } from '../config-provider/ConfigProvider';
|
||||
import { Tooltip } from '../tooltip';
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
|
||||
export const Menu = React.forwardRef<MenuRef, MenuProps & { wrapperClass?: string }>((props, ref) => {
|
||||
const { mountContainer } = useContext(ConfigContext);
|
||||
return mountContainer && <RcMenu ref={ref} prefixCls={clsx(styles.menu, props.className)} getPopupContainer={() => mountContainer} {...props} />;
|
||||
return mountContainer && (
|
||||
<RcMenu
|
||||
ref={ref}
|
||||
prefixCls={clsx(styles.menu, props.className)}
|
||||
getPopupContainer={() => mountContainer}
|
||||
{...props}
|
||||
className={props.wrapperClass}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export function MenuItem(props: MenuItemProps) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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 type { DocumentDataModel, INeedCheckDisposable, Nullable } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderModule } from '@univerjs/engine-render';
|
||||
import { Disposable, Inject } from '@univerjs/core';
|
||||
import { DocCanvasPopManagerService, DocEventManagerService } from '@univerjs/docs-ui';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { DocQuickInsertPopupService } from '../services/doc-quick-insert-popup.service';
|
||||
import { QuickInsertButton } from '../views/menu';
|
||||
|
||||
export class DocQuickInsertMenuController extends Disposable implements IRenderModule {
|
||||
private _popup: Nullable<{ startIndex: number; disposable: INeedCheckDisposable }> = null;
|
||||
constructor(
|
||||
private _context: IRenderContext<DocumentDataModel>,
|
||||
@Inject(DocEventManagerService) private _docEventManagerService: DocEventManagerService,
|
||||
@Inject(DocQuickInsertPopupService) private _docQuickInsertPopupService: DocQuickInsertPopupService,
|
||||
@Inject(DocCanvasPopManagerService) private _docCanvasPopManagerService: DocCanvasPopManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._init();
|
||||
}
|
||||
|
||||
private _init() {
|
||||
this.disposeWithMe(combineLatest([this._docEventManagerService.hoverParagraphLeft$, this._docEventManagerService.hoverParagraph$]).subscribe(([left, paragraph]) => {
|
||||
const p = left ?? paragraph;
|
||||
if (!p) {
|
||||
this._hideMenu(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (p.paragraphStart === p.paragraphEnd && p.startIndex !== this._popup?.startIndex) {
|
||||
this._hideMenu(true);
|
||||
const disposable = this._docCanvasPopManagerService.attachPopupToRect(p.firstLine, {
|
||||
componentKey: QuickInsertButton.componentKey,
|
||||
direction: 'left-center',
|
||||
}, this._context.unit.getUnitId());
|
||||
|
||||
this._popup = {
|
||||
startIndex: p.startIndex,
|
||||
disposable,
|
||||
};
|
||||
} else {
|
||||
this._hideMenu(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _hideMenu(force: boolean) {
|
||||
if (this._popup && (force || this._popup.disposable.canDispose())) {
|
||||
this._popup.disposable.dispose();
|
||||
this._popup = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { DeleteSearchKeyCommand } from '../commands/commands/doc-quick-insert.co
|
||||
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 { QuickInsertButton } from '../views/menu';
|
||||
import { QuickInsertPlaceholder } from '../views/QuickInsertPlaceholder';
|
||||
import { QuickInsertPopup } from '../views/QuickInsertPopup';
|
||||
import { builtInMenus } from './built-in-menus';
|
||||
@@ -58,6 +59,7 @@ export class DocQuickInsertUIController extends Disposable {
|
||||
[QuickInsertPlaceholder.componentKey, QuickInsertPlaceholder],
|
||||
[DividerSingle.displayName, DividerSingle],
|
||||
[TextSingle.displayName, TextSingle],
|
||||
[QuickInsertButton.componentKey, QuickInsertButton],
|
||||
] as const).forEach(([name, component]) => {
|
||||
if (name) {
|
||||
this.disposeWithMe(this._componentManager.register(name, component));
|
||||
|
||||
@@ -20,7 +20,9 @@ import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing';
|
||||
import { UniverDocsDrawingUIPlugin } from '@univerjs/docs-drawing-ui';
|
||||
import { UniverDrawingPlugin } from '@univerjs/drawing';
|
||||
import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { UniverUIPlugin } from '@univerjs/ui';
|
||||
import { DocQuickInsertMenuController } from './controllers/doc-quick-insert-menu.controller';
|
||||
import { DocQuickInsertTriggerController } from './controllers/doc-quick-insert-trigger.controller';
|
||||
import { DocQuickInsertUIController } from './controllers/doc-quick-insert-ui.controller';
|
||||
import { DocQuickInsertPopupService } from './services/doc-quick-insert-popup.service';
|
||||
@@ -33,7 +35,8 @@ export class UniverDocsQuickInsertUIPlugin extends Plugin {
|
||||
static override pluginName = PLUGIN_NAME;
|
||||
|
||||
constructor(
|
||||
@Inject(Injector) protected _injector: Injector
|
||||
@Inject(Injector) protected _injector: Injector,
|
||||
@Inject(IRenderManagerService) private _renderManagerSrv: IRenderManagerService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -53,5 +56,11 @@ export class UniverDocsQuickInsertUIPlugin extends Plugin {
|
||||
override onRendered(): void {
|
||||
this._injector.get(DocQuickInsertTriggerController);
|
||||
this._injector.get(DocQuickInsertPopupService);
|
||||
|
||||
([
|
||||
[DocQuickInsertMenuController],
|
||||
] as Dependency[]).forEach((m) => {
|
||||
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_DOC, m);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ 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 { DocCanvasPopManagerService, DocEventManagerService } 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';
|
||||
@@ -96,6 +96,10 @@ export class DocQuickInsertPopupService extends Disposable {
|
||||
mount: () => void;
|
||||
} | null = null;
|
||||
|
||||
private getDocEventManagerService(unitId: string) {
|
||||
return this._renderManagerService.getRenderById(unitId)?.with(DocEventManagerService);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(DocCanvasPopManagerService) private readonly _docCanvasPopupManagerService: DocCanvasPopManagerService,
|
||||
@Inject(IUniverInstanceService) private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@@ -192,6 +196,15 @@ export class DocQuickInsertPopupService extends Disposable {
|
||||
const { popup, index, unitId } = options;
|
||||
|
||||
this.closePopup();
|
||||
const currentDoc = this._univerInstanceService.getUnit<DocumentDataModel>(unitId);
|
||||
const paragraph = currentDoc?.getBody()?.paragraphs?.find((p) => p.startIndex > index);
|
||||
if (!paragraph) {
|
||||
return;
|
||||
}
|
||||
const paragraphBound = this.getDocEventManagerService(unitId)?.paragraphBounds.get(paragraph.startIndex);
|
||||
if (!paragraphBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._inputPlaceholderRenderRoot = this._createInputPlaceholderRenderRoot(() => {
|
||||
const docSkeletonManagerService = this._renderManagerService.getRenderById(unitId)?.with(DocSkeletonManagerService);
|
||||
@@ -224,8 +237,8 @@ export class DocQuickInsertPopupService extends Disposable {
|
||||
});
|
||||
this._inputPlaceholderRenderRoot.mount();
|
||||
|
||||
const disposable = this._docCanvasPopupManagerService.attachPopupToRange(
|
||||
{ startOffset: index, endOffset: index, collapsed: true },
|
||||
const disposable = this._docCanvasPopupManagerService.attachPopupToRect(
|
||||
paragraphBound.firstLine,
|
||||
{
|
||||
componentKey: QuickInsertPopup.componentKey,
|
||||
onClickOutside: () => {
|
||||
|
||||
@@ -280,23 +280,12 @@ export const QuickInsertPopup = () => {
|
||||
const Placeholder = currentPopup?.popup.Placeholder || componentManager.get(QuickInsertPlaceholder.componentKey);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(`
|
||||
univer-rounded-lg univer-border univer-border-solid univer-border-gray-100 univer-bg-white
|
||||
univer-shadow-[0_0_10px_0_rgba(0,0,0,0.1)]
|
||||
`)}
|
||||
>
|
||||
<div className={clsx('univer-mt-2')}>
|
||||
{hasMenus
|
||||
? (
|
||||
<div
|
||||
className={`
|
||||
univer-max-h-[360px] univer-w-[220px] univer-overflow-y-auto univer-overflow-x-hidden
|
||||
`}
|
||||
>
|
||||
<Menu>
|
||||
{renderMenus(filteredMenus)}
|
||||
</Menu>
|
||||
</div>
|
||||
<Menu wrapperClass="univer-max-h-[360px] univer-w-[220px]">
|
||||
{renderMenus(filteredMenus)}
|
||||
</Menu>
|
||||
)
|
||||
: Placeholder && <Placeholder />}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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 type { DocumentDataModel } from '@univerjs/core';
|
||||
import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { DocEventManagerService } from '@univerjs/docs-ui';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { PlusSingle } from '@univerjs/icons';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { useMemo } from 'react';
|
||||
import { DocQuickInsertPopupService } from '../../services/doc-quick-insert-popup.service';
|
||||
|
||||
interface IQuickInsertButtonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const QuickInsertButton = ({
|
||||
className = '',
|
||||
}: IQuickInsertButtonProps) => {
|
||||
const docQuickInsertPopupService = useDependency(DocQuickInsertPopupService);
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const renderManagerService = useDependency(IRenderManagerService);
|
||||
const currentDoc = useObservable(useMemo(() => univerInstanceService.getCurrentTypeOfUnit$<DocumentDataModel>(UniverInstanceType.UNIVER_DOC), [univerInstanceService]));
|
||||
const currentUnit = currentDoc && renderManagerService.getRenderById(currentDoc.getUnitId());
|
||||
const docEventManagerService = currentUnit?.with(DocEventManagerService);
|
||||
const paragraph = useObservable(docEventManagerService?.hoverParagraph$);
|
||||
const paragraphLeft = useObservable(docEventManagerService?.hoverParagraphLeft$);
|
||||
|
||||
const onClick = () => {
|
||||
const p = paragraph ?? paragraphLeft;
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
const popup = docQuickInsertPopupService.resolvePopup('/');
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
docQuickInsertPopupService.showPopup({
|
||||
popup,
|
||||
index: p.startIndex - 1,
|
||||
unitId: currentDoc?.getUnitId() ?? '',
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
univer-mr-1 univer-flex univer-cursor-pointer univer-items-center univer-gap-2.5 univer-rounded-full
|
||||
univer-border univer-border-gray-200 univer-bg-white univer-p-1.5 univer-shadow-sm
|
||||
hover:univer-bg-gray-100
|
||||
${className}
|
||||
`}
|
||||
onClick={onClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<PlusSingle className="univer-text-gray-800" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
QuickInsertButton.componentKey = 'doc.quick-insert.button';
|
||||
@@ -64,7 +64,9 @@ export const ParagraphMenu = ({ popup }: { popup: IPopup }) => {
|
||||
ref={anchorRef}
|
||||
className={`
|
||||
univer-mr-1 univer-inline-flex univer-h-7 univer-cursor-pointer univer-items-center univer-gap-1
|
||||
univer-rounded-full univer-bg-[#EEEFF1] univer-px-2.5 univer-py-0
|
||||
univer-rounded-full univer-border univer-border-solid univer-border-gray-200 univer-px-2.5 univer-py-0
|
||||
hover:univer-bg-gray-100
|
||||
${visible ? 'univer-bg-gray-100' : 'univer-bg-white'}
|
||||
`}
|
||||
onMouseEnter={(e) => {
|
||||
popup.onPointerEnter?.(e);
|
||||
|
||||
@@ -174,6 +174,5 @@ export function DocInsertBellowMenuItemFactory(accessor: IAccessor): IMenuSelect
|
||||
id: INSERT_BELLOW_MENU_ID,
|
||||
type: MenuItemType.SUBITEMS,
|
||||
title: 'rightClick.insertBellow',
|
||||
icon: 'FreezeToSelectedSingle',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,7 +137,8 @@ export class DocParagraphMenuService extends Disposable implements IRenderModule
|
||||
const dataStream = this._context.unit.getBody()?.dataStream ?? '';
|
||||
const paragraphDataStream = paragraph ? dataStream.slice(paragraph.paragraphStart, paragraph.paragraphEnd) : '';
|
||||
const isOnlyImage = paragraphDataStream === '\b';
|
||||
const shouldHidden = isOnlyImage;
|
||||
const isEmptyParagraph = paragraphDataStream === '';
|
||||
const shouldHidden = isOnlyImage || isEmptyParagraph;
|
||||
|
||||
if (shouldHidden) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user