[WIKI-638] fix: peek overview closing while dropdowns are open (#7841)
This commit is contained in:
parent
0ed49a6989
commit
dce8b75a1e
8 changed files with 78 additions and 23 deletions
|
|
@ -29,7 +29,8 @@ import { IssueTitleInput } from "../title-input";
|
||||||
// services init
|
// services init
|
||||||
const workItemVersionService = new WorkItemVersionService();
|
const workItemVersionService = new WorkItemVersionService();
|
||||||
|
|
||||||
interface IPeekOverviewIssueDetails {
|
type Props = {
|
||||||
|
editorRef: React.RefObject<EditorRefApi>;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
issueId: string;
|
issueId: string;
|
||||||
|
|
@ -38,12 +39,11 @@ interface IPeekOverviewIssueDetails {
|
||||||
isArchived: boolean;
|
isArchived: boolean;
|
||||||
isSubmitting: TNameDescriptionLoader;
|
isSubmitting: TNameDescriptionLoader;
|
||||||
setIsSubmitting: (value: TNameDescriptionLoader) => void;
|
setIsSubmitting: (value: TNameDescriptionLoader) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = observer((props) => {
|
export const PeekOverviewIssueDetails: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, issueId, issueOperations, disabled, isArchived, isSubmitting, setIsSubmitting } = props;
|
const { editorRef, workspaceSlug, issueId, issueOperations, disabled, isArchived, isSubmitting, setIsSubmitting } =
|
||||||
// refs
|
props;
|
||||||
const editorRef = useRef<EditorRefApi>(null);
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { FC, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
// plane imports
|
// plane imports
|
||||||
|
import type { EditorRefApi } from "@plane/editor";
|
||||||
import { EIssueServiceType, TNameDescriptionLoader } from "@plane/types";
|
import { EIssueServiceType, TNameDescriptionLoader } from "@plane/types";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -53,6 +54,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
const [isEditIssueModalOpen, setIsEditIssueModalOpen] = useState(false);
|
const [isEditIssueModalOpen, setIsEditIssueModalOpen] = useState(false);
|
||||||
// ref
|
// ref
|
||||||
const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
|
const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
|
||||||
|
const editorRef = useRef<EditorRefApi>(null);
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
setPeekIssue,
|
setPeekIssue,
|
||||||
|
|
@ -80,8 +82,9 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
usePeekOverviewOutsideClickDetector(
|
usePeekOverviewOutsideClickDetector(
|
||||||
issuePeekOverviewRef,
|
issuePeekOverviewRef,
|
||||||
() => {
|
() => {
|
||||||
|
const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen();
|
||||||
if (!embedIssue) {
|
if (!embedIssue) {
|
||||||
if (!isAnyModalOpen && !isAnyEpicModalOpen && !isAnyLocalModalOpen) {
|
if (!isAnyModalOpen && !isAnyEpicModalOpen && !isAnyLocalModalOpen && !isAnyDropbarOpen) {
|
||||||
removeRoutePeekId();
|
removeRoutePeekId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,10 +93,10 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleKeyDown = () => {
|
const handleKeyDown = () => {
|
||||||
const slashCommandDropdownElement = document.querySelector("#slash-command");
|
|
||||||
const editorImageFullScreenModalElement = document.querySelector(".editor-image-full-screen-modal");
|
const editorImageFullScreenModalElement = document.querySelector(".editor-image-full-screen-modal");
|
||||||
const dropdownElement = document.activeElement?.tagName === "INPUT";
|
const dropdownElement = document.activeElement?.tagName === "INPUT";
|
||||||
if (!isAnyModalOpen && !slashCommandDropdownElement && !dropdownElement && !editorImageFullScreenModalElement) {
|
const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen();
|
||||||
|
if (!isAnyModalOpen && !dropdownElement && !isAnyDropbarOpen && !editorImageFullScreenModalElement) {
|
||||||
removeRoutePeekId();
|
removeRoutePeekId();
|
||||||
const issueElement = document.getElementById(`issue-${issueId}`);
|
const issueElement = document.getElementById(`issue-${issueId}`);
|
||||||
if (issueElement) issueElement?.focus();
|
if (issueElement) issueElement?.focus();
|
||||||
|
|
@ -166,6 +169,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
{["side-peek", "modal"].includes(peekMode) ? (
|
{["side-peek", "modal"].includes(peekMode) ? (
|
||||||
<div className="relative flex flex-col gap-3 px-8 py-5 space-y-3">
|
<div className="relative flex flex-col gap-3 px-8 py-5 space-y-3">
|
||||||
<PeekOverviewIssueDetails
|
<PeekOverviewIssueDetails
|
||||||
|
editorRef={editorRef}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
|
@ -206,6 +210,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
<div className="relative h-full w-full space-y-6 overflow-auto p-4 py-5">
|
<div className="relative h-full w-full space-y-6 overflow-auto p-4 py-5">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<PeekOverviewIssueDetails
|
<PeekOverviewIssueDetails
|
||||||
|
editorRef={editorRef}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,14 @@ const Command = Extension.create<SlashCommandOptions>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderItems = () => {
|
const renderItems: SuggestionOptions["render"] = () => {
|
||||||
let component: ReactRenderer<CommandListInstance, SlashCommandsMenuProps> | null = null;
|
let component: ReactRenderer<CommandListInstance, SlashCommandsMenuProps> | null = null;
|
||||||
let popup: Instance | null = null;
|
let popup: Instance | null = null;
|
||||||
return {
|
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<CommandListInstance, SlashCommandsMenuProps>(SlashCommandsMenu, {
|
component = new ReactRenderer<CommandListInstance, SlashCommandsMenuProps>(SlashCommandsMenu, {
|
||||||
props,
|
props,
|
||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
|
|
@ -78,14 +81,14 @@ const renderItems = () => {
|
||||||
placement: "bottom-start",
|
placement: "bottom-start",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onUpdate: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
|
onUpdate: (props) => {
|
||||||
component?.updateProps(props);
|
component?.updateProps(props);
|
||||||
|
|
||||||
popup?.[0]?.setProps({
|
popup?.[0]?.setProps({
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onKeyDown: (props: { event: KeyboardEvent }) => {
|
onKeyDown: (props) => {
|
||||||
if (props.event.key === "Escape") {
|
if (props.event.key === "Escape") {
|
||||||
popup?.[0].hide();
|
popup?.[0].hide();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -95,7 +98,9 @@ const renderItems = () => {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onExit: () => {
|
onExit: ({ editor }) => {
|
||||||
|
// Remove from active dropdowns
|
||||||
|
editor?.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.SLASH_COMMANDS);
|
||||||
popup?.[0].destroy();
|
popup?.[0].destroy();
|
||||||
component?.destroy();
|
component?.destroy();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import { Ellipsis } from "lucide-react";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
|
// constants
|
||||||
|
import { CORE_EXTENSIONS } from "@/constants/extension";
|
||||||
// extensions
|
// extensions
|
||||||
import {
|
import {
|
||||||
findTable,
|
findTable,
|
||||||
|
|
@ -59,7 +61,16 @@ export const ColumnDragHandle: React.FC<ColumnDragHandleProps> = (props) => {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
open: isDropdownOpen,
|
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,
|
whileElementsMounted: autoUpdate,
|
||||||
});
|
});
|
||||||
const click = useClick(context);
|
const click = useClick(context);
|
||||||
|
|
@ -185,7 +196,6 @@ export const ColumnDragHandle: React.FC<ColumnDragHandleProps> = (props) => {
|
||||||
}}
|
}}
|
||||||
lockScroll
|
lockScroll
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="max-h-[90vh] w-[12rem] overflow-y-auto rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 shadow-custom-shadow-rg"
|
className="max-h-[90vh] w-[12rem] overflow-y-auto rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 shadow-custom-shadow-rg"
|
||||||
ref={refs.setFloating}
|
ref={refs.setFloating}
|
||||||
|
|
@ -195,7 +205,7 @@ export const ColumnDragHandle: React.FC<ColumnDragHandleProps> = (props) => {
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ColumnOptionsDropdown editor={editor} onClose={() => setIsDropdownOpen(false)} />
|
<ColumnOptionsDropdown editor={editor} onClose={() => context.onOpenChange(false)} />
|
||||||
</div>
|
</div>
|
||||||
</FloatingPortal>
|
</FloatingPortal>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import { Ellipsis } from "lucide-react";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
|
// constants
|
||||||
|
import { CORE_EXTENSIONS } from "@/constants/extension";
|
||||||
// extensions
|
// extensions
|
||||||
import {
|
import {
|
||||||
findTable,
|
findTable,
|
||||||
|
|
@ -59,7 +61,16 @@ export const RowDragHandle: React.FC<RowDragHandleProps> = (props) => {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
open: isDropdownOpen,
|
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,
|
whileElementsMounted: autoUpdate,
|
||||||
});
|
});
|
||||||
const click = useClick(context);
|
const click = useClick(context);
|
||||||
|
|
@ -184,7 +195,6 @@ export const RowDragHandle: React.FC<RowDragHandleProps> = (props) => {
|
||||||
}}
|
}}
|
||||||
lockScroll
|
lockScroll
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="max-h-[90vh] w-[12rem] overflow-y-auto rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 shadow-custom-shadow-rg"
|
className="max-h-[90vh] w-[12rem] overflow-y-auto rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 shadow-custom-shadow-rg"
|
||||||
ref={refs.setFloating}
|
ref={refs.setFloating}
|
||||||
|
|
@ -194,7 +204,7 @@ export const RowDragHandle: React.FC<RowDragHandleProps> = (props) => {
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RowOptionsDropdown editor={editor} onClose={() => setIsDropdownOpen(false)} />
|
<RowOptionsDropdown editor={editor} onClose={() => context.onOpenChange(false)} />
|
||||||
</div>
|
</div>
|
||||||
</FloatingPortal>
|
</FloatingPortal>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,18 @@ import { DropHandlerPlugin } from "@/plugins/drop";
|
||||||
import { FilePlugins } from "@/plugins/file/root";
|
import { FilePlugins } from "@/plugins/file/root";
|
||||||
import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard";
|
import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard";
|
||||||
// types
|
// types
|
||||||
|
|
||||||
import type { IEditorProps, TEditorAsset, TFileHandler } from "@/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" {
|
declare module "@tiptap/core" {
|
||||||
interface Commands {
|
interface Commands {
|
||||||
utility: {
|
[CORE_EXTENSIONS.UTILITY]: {
|
||||||
updateAssetsUploadStatus: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void;
|
updateAssetsUploadStatus: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void;
|
||||||
updateAssetsList: (
|
updateAssetsList: (
|
||||||
args:
|
args:
|
||||||
|
|
@ -26,6 +31,8 @@ declare module "@tiptap/core" {
|
||||||
idToRemove: string;
|
idToRemove: string;
|
||||||
}
|
}
|
||||||
) => () => void;
|
) => () => void;
|
||||||
|
addActiveDropbarExtension: (extension: TActiveDropbarExtensions) => () => void;
|
||||||
|
removeActiveDropbarExtension: (extension: TActiveDropbarExtensions) => () => void;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +109,18 @@ export const UtilityExtension = (props: Props) => {
|
||||||
}
|
}
|
||||||
this.storage.assetsList = Array.from(uniqueAssets);
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,11 @@ export const getEditorRefHelpers = (args: TArgs): EditorRefApi => {
|
||||||
const markdownOutput = editor?.storage?.markdown?.getMarkdown?.();
|
const markdownOutput = editor?.storage?.markdown?.getMarkdown?.();
|
||||||
return markdownOutput;
|
return markdownOutput;
|
||||||
},
|
},
|
||||||
|
isAnyDropbarOpen: () => {
|
||||||
|
if (!editor) return false;
|
||||||
|
const utilityStorage = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY);
|
||||||
|
return utilityStorage.activeDropbarExtensions.length > 0;
|
||||||
|
},
|
||||||
scrollSummary: (marking) => {
|
scrollSummary: (marking) => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
scrollSummary(editor, marking);
|
scrollSummary(editor, marking);
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,7 @@ export type EditorRefApi = {
|
||||||
getMarkDown: () => string;
|
getMarkDown: () => string;
|
||||||
getSelectedText: () => string | null;
|
getSelectedText: () => string | null;
|
||||||
insertText: (contentHTML: string, insertOnNextLine?: boolean) => void;
|
insertText: (contentHTML: string, insertOnNextLine?: boolean) => void;
|
||||||
|
isAnyDropbarOpen: () => boolean;
|
||||||
isEditorReadyToDiscard: () => boolean;
|
isEditorReadyToDiscard: () => boolean;
|
||||||
isMenuItemActive: <T extends TEditorCommands>(props: TCommandWithPropsWithItemKey<T>) => boolean;
|
isMenuItemActive: <T extends TEditorCommands>(props: TCommandWithPropsWithItemKey<T>) => boolean;
|
||||||
listenToRealTimeUpdate: () => TDocumentEventEmitter | undefined;
|
listenToRealTimeUpdate: () => TDocumentEventEmitter | undefined;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue