diff --git a/apps/web/core/components/issues/peek-overview/issue-detail.tsx b/apps/web/core/components/issues/peek-overview/issue-detail.tsx index 781aa623c..20d89a78f 100644 --- a/apps/web/core/components/issues/peek-overview/issue-detail.tsx +++ b/apps/web/core/components/issues/peek-overview/issue-detail.tsx @@ -29,7 +29,8 @@ import { IssueTitleInput } from "../title-input"; // services init const workItemVersionService = new WorkItemVersionService(); -interface IPeekOverviewIssueDetails { +type Props = { + editorRef: React.RefObject; workspaceSlug: string; projectId: string; issueId: string; @@ -38,12 +39,11 @@ interface IPeekOverviewIssueDetails { isArchived: boolean; isSubmitting: TNameDescriptionLoader; setIsSubmitting: (value: TNameDescriptionLoader) => void; -} +}; -export const PeekOverviewIssueDetails: FC = observer((props) => { - const { workspaceSlug, issueId, issueOperations, disabled, isArchived, isSubmitting, setIsSubmitting } = props; - // refs - const editorRef = useRef(null); +export const PeekOverviewIssueDetails: FC = observer((props) => { + const { editorRef, workspaceSlug, issueId, issueOperations, disabled, isArchived, isSubmitting, setIsSubmitting } = + props; // store hooks const { data: currentUser } = useUser(); const { diff --git a/apps/web/core/components/issues/peek-overview/view.tsx b/apps/web/core/components/issues/peek-overview/view.tsx index bbae72db3..9840de4ce 100644 --- a/apps/web/core/components/issues/peek-overview/view.tsx +++ b/apps/web/core/components/issues/peek-overview/view.tsx @@ -2,6 +2,7 @@ import { FC, useRef, useState } from "react"; import { observer } from "mobx-react"; import { createPortal } from "react-dom"; // plane imports +import type { EditorRefApi } from "@plane/editor"; import { EIssueServiceType, TNameDescriptionLoader } from "@plane/types"; import { cn } from "@plane/utils"; // hooks @@ -53,6 +54,7 @@ export const IssueView: FC = observer((props) => { const [isEditIssueModalOpen, setIsEditIssueModalOpen] = useState(false); // ref const issuePeekOverviewRef = useRef(null); + const editorRef = useRef(null); // store hooks const { setPeekIssue, @@ -80,8 +82,9 @@ export const IssueView: FC = observer((props) => { usePeekOverviewOutsideClickDetector( issuePeekOverviewRef, () => { + const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen(); if (!embedIssue) { - if (!isAnyModalOpen && !isAnyEpicModalOpen && !isAnyLocalModalOpen) { + if (!isAnyModalOpen && !isAnyEpicModalOpen && !isAnyLocalModalOpen && !isAnyDropbarOpen) { removeRoutePeekId(); } } @@ -90,10 +93,10 @@ export const IssueView: FC = observer((props) => { ); const handleKeyDown = () => { - const slashCommandDropdownElement = document.querySelector("#slash-command"); const editorImageFullScreenModalElement = document.querySelector(".editor-image-full-screen-modal"); const dropdownElement = document.activeElement?.tagName === "INPUT"; - if (!isAnyModalOpen && !slashCommandDropdownElement && !dropdownElement && !editorImageFullScreenModalElement) { + const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen(); + if (!isAnyModalOpen && !dropdownElement && !isAnyDropbarOpen && !editorImageFullScreenModalElement) { removeRoutePeekId(); const issueElement = document.getElementById(`issue-${issueId}`); if (issueElement) issueElement?.focus(); @@ -166,6 +169,7 @@ export const IssueView: FC = observer((props) => { {["side-peek", "modal"].includes(peekMode) ? (
= observer((props) => {
({ }, }); -const renderItems = () => { +const renderItems: SuggestionOptions["render"] = () => { let component: ReactRenderer | null = null; let popup: Instance | null = null; return { - onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => { + onStart: (props) => { + // Track active dropdown + props.editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.SLASH_COMMANDS); + component = new ReactRenderer(SlashCommandsMenu, { props, editor: props.editor, @@ -78,14 +81,14 @@ const renderItems = () => { placement: "bottom-start", }); }, - onUpdate: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => { + onUpdate: (props) => { component?.updateProps(props); popup?.[0]?.setProps({ getReferenceClientRect: props.clientRect, }); }, - onKeyDown: (props: { event: KeyboardEvent }) => { + onKeyDown: (props) => { if (props.event.key === "Escape") { popup?.[0].hide(); return true; @@ -95,7 +98,9 @@ const renderItems = () => { } return false; }, - onExit: () => { + onExit: ({ editor }) => { + // Remove from active dropdowns + editor?.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.SLASH_COMMANDS); popup?.[0].destroy(); component?.destroy(); }, diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx index 425bc7572..b4322b238 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx @@ -15,6 +15,8 @@ import { Ellipsis } from "lucide-react"; import { useCallback, useState } from "react"; // plane imports import { cn } from "@plane/utils"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { findTable, @@ -59,7 +61,16 @@ export const ColumnDragHandle: React.FC = (props) => { }), ], open: isDropdownOpen, - onOpenChange: setIsDropdownOpen, + onOpenChange: (open) => { + setIsDropdownOpen(open); + if (open) { + editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE); + } else { + setTimeout(() => { + editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE); + }, 0); + } + }, whileElementsMounted: autoUpdate, }); const click = useClick(context); @@ -185,7 +196,6 @@ export const ColumnDragHandle: React.FC = (props) => { }} lockScroll /> -
= (props) => { zIndex: 100, }} > - setIsDropdownOpen(false)} /> + context.onOpenChange(false)} />
)} diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx index 7c0f1449a..d556e71bc 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx @@ -15,6 +15,8 @@ import { Ellipsis } from "lucide-react"; import { useCallback, useState } from "react"; // plane imports import { cn } from "@plane/utils"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { findTable, @@ -59,7 +61,16 @@ export const RowDragHandle: React.FC = (props) => { }), ], open: isDropdownOpen, - onOpenChange: setIsDropdownOpen, + onOpenChange: (open) => { + setIsDropdownOpen(open); + if (open) { + editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE); + } else { + setTimeout(() => { + editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE); + }, 0); + } + }, whileElementsMounted: autoUpdate, }); const click = useClick(context); @@ -184,7 +195,6 @@ export const RowDragHandle: React.FC = (props) => { }} lockScroll /> -
= (props) => { zIndex: 100, }} > - setIsDropdownOpen(false)} /> + context.onOpenChange(false)} />
)} diff --git a/packages/editor/src/core/extensions/utility.ts b/packages/editor/src/core/extensions/utility.ts index 3c014538d..77fdcc912 100644 --- a/packages/editor/src/core/extensions/utility.ts +++ b/packages/editor/src/core/extensions/utility.ts @@ -9,13 +9,18 @@ import { DropHandlerPlugin } from "@/plugins/drop"; import { FilePlugins } from "@/plugins/file/root"; import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard"; // types - import type { IEditorProps, TEditorAsset, TFileHandler } from "@/types"; -type TActiveDropbarExtensions = CORE_EXTENSIONS.MENTION | CORE_EXTENSIONS.EMOJI | TAdditionalActiveDropbarExtensions; + +type TActiveDropbarExtensions = + | CORE_EXTENSIONS.MENTION + | CORE_EXTENSIONS.EMOJI + | CORE_EXTENSIONS.SLASH_COMMANDS + | CORE_EXTENSIONS.TABLE + | TAdditionalActiveDropbarExtensions; declare module "@tiptap/core" { interface Commands { - utility: { + [CORE_EXTENSIONS.UTILITY]: { updateAssetsUploadStatus: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void; updateAssetsList: ( args: @@ -26,6 +31,8 @@ declare module "@tiptap/core" { idToRemove: string; } ) => () => void; + addActiveDropbarExtension: (extension: TActiveDropbarExtensions) => () => void; + removeActiveDropbarExtension: (extension: TActiveDropbarExtensions) => () => void; }; } } @@ -102,6 +109,18 @@ export const UtilityExtension = (props: Props) => { } this.storage.assetsList = Array.from(uniqueAssets); }, + addActiveDropbarExtension: (extension) => () => { + const index = this.storage.activeDropbarExtensions.indexOf(extension); + if (index === -1) { + this.storage.activeDropbarExtensions.push(extension); + } + }, + removeActiveDropbarExtension: (extension) => () => { + const index = this.storage.activeDropbarExtensions.indexOf(extension); + if (index !== -1) { + this.storage.activeDropbarExtensions.splice(index, 1); + } + }, }; }, }); diff --git a/packages/editor/src/core/helpers/editor-ref.ts b/packages/editor/src/core/helpers/editor-ref.ts index 49d20df94..2e45e8963 100644 --- a/packages/editor/src/core/helpers/editor-ref.ts +++ b/packages/editor/src/core/helpers/editor-ref.ts @@ -81,6 +81,11 @@ export const getEditorRefHelpers = (args: TArgs): EditorRefApi => { const markdownOutput = editor?.storage?.markdown?.getMarkdown?.(); return markdownOutput; }, + isAnyDropbarOpen: () => { + if (!editor) return false; + const utilityStorage = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY); + return utilityStorage.activeDropbarExtensions.length > 0; + }, scrollSummary: (marking) => { if (!editor) return; scrollSummary(editor, marking); diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 6c7254137..a99870c05 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -119,6 +119,7 @@ export type EditorRefApi = { getMarkDown: () => string; getSelectedText: () => string | null; insertText: (contentHTML: string, insertOnNextLine?: boolean) => void; + isAnyDropbarOpen: () => boolean; isEditorReadyToDiscard: () => boolean; isMenuItemActive: (props: TCommandWithPropsWithItemKey) => boolean; listenToRealTimeUpdate: () => TDocumentEventEmitter | undefined;