diff --git a/packages/design/src/components/menu/Menu.tsx b/packages/design/src/components/menu/Menu.tsx index 820e6e56b9..f438234b50 100644 --- a/packages/design/src/components/menu/Menu.tsx +++ b/packages/design/src/components/menu/Menu.tsx @@ -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((props, ref) => { +export const Menu = React.forwardRef((props, ref) => { const { mountContainer } = useContext(ConfigContext); - return mountContainer && mountContainer} {...props} />; + return mountContainer && ( + mountContainer} + {...props} + className={props.wrapperClass} + /> + ); }); export function MenuItem(props: MenuItemProps) { diff --git a/packages/docs-quick-insert-ui/src/controllers/doc-quick-insert-menu.controller.ts b/packages/docs-quick-insert-ui/src/controllers/doc-quick-insert-menu.controller.ts new file mode 100644 index 0000000000..4f5c7b8353 --- /dev/null +++ b/packages/docs-quick-insert-ui/src/controllers/doc-quick-insert-menu.controller.ts @@ -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, + @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; + } + } +} diff --git a/packages/docs-quick-insert-ui/src/controllers/doc-quick-insert-ui.controller.ts b/packages/docs-quick-insert-ui/src/controllers/doc-quick-insert-ui.controller.ts index 0048da88a0..d52716e09b 100644 --- a/packages/docs-quick-insert-ui/src/controllers/doc-quick-insert-ui.controller.ts +++ b/packages/docs-quick-insert-ui/src/controllers/doc-quick-insert-ui.controller.ts @@ -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)); diff --git a/packages/docs-quick-insert-ui/src/plugin.ts b/packages/docs-quick-insert-ui/src/plugin.ts index 01fb33d980..44e2772bc6 100644 --- a/packages/docs-quick-insert-ui/src/plugin.ts +++ b/packages/docs-quick-insert-ui/src/plugin.ts @@ -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); + }); } } diff --git a/packages/docs-quick-insert-ui/src/services/doc-quick-insert-popup.service.ts b/packages/docs-quick-insert-ui/src/services/doc-quick-insert-popup.service.ts index c507f49711..a7d8f7cf6b 100644 --- a/packages/docs-quick-insert-ui/src/services/doc-quick-insert-popup.service.ts +++ b/packages/docs-quick-insert-ui/src/services/doc-quick-insert-popup.service.ts @@ -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(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: () => { diff --git a/packages/docs-quick-insert-ui/src/views/QuickInsertPopup.tsx b/packages/docs-quick-insert-ui/src/views/QuickInsertPopup.tsx index 4545856ec5..508b99b35b 100644 --- a/packages/docs-quick-insert-ui/src/views/QuickInsertPopup.tsx +++ b/packages/docs-quick-insert-ui/src/views/QuickInsertPopup.tsx @@ -280,23 +280,12 @@ export const QuickInsertPopup = () => { const Placeholder = currentPopup?.popup.Placeholder || componentManager.get(QuickInsertPlaceholder.componentKey); return ( -
+
{hasMenus ? ( -
- - {renderMenus(filteredMenus)} - -
+ + {renderMenus(filteredMenus)} + ) : Placeholder && }
diff --git a/packages/docs-quick-insert-ui/src/views/menu/index.tsx b/packages/docs-quick-insert-ui/src/views/menu/index.tsx new file mode 100644 index 0000000000..0cd60c2b3e --- /dev/null +++ b/packages/docs-quick-insert-ui/src/views/menu/index.tsx @@ -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$(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 ( +
+ +
+ ); +}; + +QuickInsertButton.componentKey = 'doc.quick-insert.button'; diff --git a/packages/docs-ui/src/components/paragraph-menu/index.tsx b/packages/docs-ui/src/components/paragraph-menu/index.tsx index 359c1749a1..ca571459cb 100644 --- a/packages/docs-ui/src/components/paragraph-menu/index.tsx +++ b/packages/docs-ui/src/components/paragraph-menu/index.tsx @@ -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); diff --git a/packages/docs-ui/src/controllers/menu/paragraph-menu.ts b/packages/docs-ui/src/controllers/menu/paragraph-menu.ts index ee94065653..9b1218c804 100644 --- a/packages/docs-ui/src/controllers/menu/paragraph-menu.ts +++ b/packages/docs-ui/src/controllers/menu/paragraph-menu.ts @@ -174,6 +174,5 @@ export function DocInsertBellowMenuItemFactory(accessor: IAccessor): IMenuSelect id: INSERT_BELLOW_MENU_ID, type: MenuItemType.SUBITEMS, title: 'rightClick.insertBellow', - icon: 'FreezeToSelectedSingle', }; } diff --git a/packages/docs-ui/src/services/doc-paragraph-menu.service.ts b/packages/docs-ui/src/services/doc-paragraph-menu.service.ts index 85635bc3b3..75a062e886 100644 --- a/packages/docs-ui/src/services/doc-paragraph-menu.service.ts +++ b/packages/docs-ui/src/services/doc-paragraph-menu.service.ts @@ -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;