diff --git a/packages/editor/document-editor/src/ui/extensions/index.tsx b/packages/editor/document-editor/src/ui/extensions/index.tsx index 7de1e2922..2576d0d74 100644 --- a/packages/editor/document-editor/src/ui/extensions/index.tsx +++ b/packages/editor/document-editor/src/ui/extensions/index.tsx @@ -1,56 +1,29 @@ import Placeholder from "@tiptap/extension-placeholder"; -import { IssueWidgetExtension } from "src/ui/extensions/widgets/issue-embed-widget"; - -import { IIssueEmbedConfig } from "src/ui/extensions/widgets/issue-embed-widget/types"; +import { IssueWidgetPlaceholder } from "src/ui/extensions/widgets/issue-embed-widget"; import { SlashCommand, DragAndDrop } from "@plane/editor-extensions"; -import { ISlashCommandItem, UploadImage } from "@plane/editor-core"; -import { IssueSuggestions } from "src/ui/extensions/widgets/issue-embed-suggestion-list"; -import { LayersIcon } from "@plane/ui"; +import { UploadImage } from "@plane/editor-core"; export const DocumentEditorExtensions = ( uploadFile: UploadImage, - issueEmbedConfig?: IIssueEmbedConfig, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void, - setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void -) => { - const additionalOptions: ISlashCommandItem[] = [ - { - key: "issue_embed", - title: "Issue embed", - description: "Embed an issue from the project.", - searchTerms: ["issue", "link", "embed"], - icon: , - command: ({ editor, range }) => { - editor - .chain() - .focus() - .insertContentAt( - range, - "

#issue_

\n" - ) - .run(); - }, + setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void, + setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void +) => [ + SlashCommand(uploadFile, setIsSubmitting), + DragAndDrop(setHideDragHandle), + Placeholder.configure({ + placeholder: ({ node }) => { + if (node.type.name === "heading") { + return `Heading ${node.attrs.level}`; + } + if (node.type.name === "image" || node.type.name === "table") { + return ""; + } + + return "Press '/' for commands..."; }, - ]; + includeChildren: true, + }), + IssueWidgetPlaceholder(), +]; - return [ - SlashCommand(uploadFile, setIsSubmitting, additionalOptions), - DragAndDrop(setHideDragHandle), - Placeholder.configure({ - placeholder: ({ node }) => { - if (node.type.name === "heading") { - return `Heading ${node.attrs.level}`; - } - if (node.type.name === "image" || node.type.name === "table") { - return ""; - } - - return "Press '/' for commands..."; - }, - includeChildren: true, - }), - IssueWidgetExtension({ issueEmbedConfig }), - IssueSuggestions(issueEmbedConfig ? issueEmbedConfig.issues : []), - ]; -}; diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx index 637afe29c..869c7a8c6 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx @@ -78,7 +78,6 @@ const IssueSuggestionList = ({ const navigationKeys = ["ArrowUp", "ArrowDown", "Enter", "Tab"]; const onKeyDown = (e: KeyboardEvent) => { if (navigationKeys.includes(e.key)) { - e.preventDefault(); // if (editor.isFocused) { // editor.chain().blur(); // commandListContainer.current?.focus(); @@ -87,7 +86,6 @@ const IssueSuggestionList = ({ setSelectedIndex( (selectedIndex + displayedItems[currentSection].length - 1) % displayedItems[currentSection].length ); - e.stopPropagation(); return true; } if (e.key === "ArrowDown") { @@ -102,12 +100,10 @@ const IssueSuggestionList = ({ [currentSection]: [...prevItems[currentSection], ...nextItems], })); } - e.stopPropagation(); return true; } if (e.key === "Enter") { selectItem(currentSection, selectedIndex); - e.stopPropagation(); return true; } if (e.key === "Tab") { @@ -115,7 +111,6 @@ const IssueSuggestionList = ({ const nextSectionIndex = (currentSectionIndex + 1) % sections.length; setCurrentSection(sections[nextSectionIndex]); setSelectedIndex(0); - e.stopPropagation(); return true; } return false; @@ -150,7 +145,7 @@ const IssueSuggestionList = ({
{sections.map((section) => { const sectionItems = displayedItems[section]; @@ -193,29 +188,35 @@ const IssueSuggestionList = ({
) : null; }; - export const IssueListRenderer = () => { let component: ReactRenderer | null = null; let popup: any | null = null; return { onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => { + const container = document.querySelector(".frame-renderer") as HTMLElement; component = new ReactRenderer(IssueSuggestionList, { props, // @ts-ignore editor: props.editor, }); - // @ts-ignore - popup = tippy("body", { + popup = tippy(".frame-renderer", { + flipbehavior: ["bottom", "top"], + appendTo: () => document.querySelector(".frame-renderer") as HTMLElement, + flip: true, + flipOnUpdate: true, getReferenceClientRect: props.clientRect, - appendTo: () => document.querySelector("#editor-container"), content: component.element, showOnCreate: true, interactive: true, trigger: "manual", placement: "bottom-start", }); + + container.addEventListener("scroll", () => { + popup?.[0].destroy(); + }); }, onUpdate: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => { component?.updateProps(props); @@ -230,10 +231,20 @@ export const IssueListRenderer = () => { popup?.[0].hide(); return true; } - // @ts-ignore - return component?.ref?.onKeyDown(props); + + const navigationKeys = ["ArrowUp", "ArrowDown", "Enter", "Tab"]; + if (navigationKeys.includes(props.event.key)) { + // @ts-ignore + component?.ref?.onKeyDown(props); + return true; + } + return false; }, onExit: (e) => { + const container = document.querySelector(".frame-renderer") as HTMLElement; + if (container) { + container.removeEventListener("scroll", () => {}); + } popup?.[0].destroy(); setTimeout(() => { component?.destroy(); diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/index.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/index.tsx index 9bbb34aa5..264a70152 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/index.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/index.tsx @@ -1,11 +1,3 @@ import { IssueWidget } from "src/ui/extensions/widgets/issue-embed-widget/issue-widget-node"; -import { IIssueEmbedConfig } from "src/ui/extensions/widgets/issue-embed-widget/types"; -interface IssueWidgetExtensionProps { - issueEmbedConfig?: IIssueEmbedConfig; -} - -export const IssueWidgetExtension = ({ issueEmbedConfig }: IssueWidgetExtensionProps) => - IssueWidget.configure({ - issueEmbedConfig, - }); +export const IssueWidgetPlaceholder = () => IssueWidget.configure({}); diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx index caca2ded7..d3b6fd04f 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx @@ -1,77 +1,33 @@ // @ts-nocheck -import { useState, useEffect } from "react"; +import { Button } from "@plane/ui"; import { NodeViewWrapper } from "@tiptap/react"; -import { Avatar, AvatarGroup, Loader, PriorityIcon } from "@plane/ui"; -import { Calendar, AlertTriangle } from "lucide-react"; +import { Crown } from "lucide-react"; -export const IssueWidgetCard = (props) => { - const [loading, setLoading] = useState(1); - const [issueDetails, setIssueDetails] = useState(); - - useEffect(() => { - props.issueEmbedConfig - .fetchIssue(props.node.attrs.entity_identifier) - .then((issue) => { - setIssueDetails(issue); - setLoading(0); - }) - .catch(() => { - setLoading(-1); - }); - }, []); - - const completeIssueEmbedAction = () => { - props.issueEmbedConfig.clickAction(issueDetails.id, props.node.attrs.title); - }; - - return ( - - {loading == 0 ? ( -
-
- {issueDetails.project_detail.identifier}-{issueDetails.sequence_id} -
-

{issueDetails.name}

-
-
- +export const IssueWidgetCard = (props) => ( + +
+
+ {props.node.attrs.project_identifier}-{props.node.attrs.sequence_id} +
+
+
+
+
+
-
- - {issueDetails.assignee_details.map((assignee) => ( - - ))} - +
+ Embed and access issues in pages seamlessly, upgrade to plane pro now.
- {issueDetails.target_date && ( -
- - {new Date(issueDetails.target_date).toLocaleDateString()} -
- )}
+ + +
- ) : loading == -1 ? ( -
- - {"This Issue embed is not found in any project. It can no longer be updated or accessed from here."} -
- ) : ( -
- - -
- - -
-
-
- )} - - ); -}; +
+
+ +); diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-node.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-node.tsx index c13637bd9..6c744927a 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-node.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-node.tsx @@ -34,9 +34,7 @@ export const IssueWidget = Node.create({ }, addNodeView() { - return ReactNodeViewRenderer((props: Object) => ( - - )); + return ReactNodeViewRenderer((props: Object) => ); }, parseHTML() { diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/types.ts b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/types.ts deleted file mode 100644 index 615b55dee..000000000 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface IEmbedConfig { - issueEmbedConfig: IIssueEmbedConfig; -} - -export interface IIssueEmbedConfig { - fetchIssue: (issueId: string) => Promise; - clickAction: (issueId: string, issueTitle: string) => void; - issues: Array; -} diff --git a/packages/editor/document-editor/src/ui/index.tsx b/packages/editor/document-editor/src/ui/index.tsx index e88632c3b..d1bdbc935 100644 --- a/packages/editor/document-editor/src/ui/index.tsx +++ b/packages/editor/document-editor/src/ui/index.tsx @@ -10,7 +10,6 @@ import { DocumentDetails } from "src/types/editor-types"; import { PageRenderer } from "src/ui/components/page-renderer"; import { getMenuOptions } from "src/utils/menu-options"; import { useRouter } from "next/router"; -import { IEmbedConfig } from "src/ui/extensions/widgets/issue-embed-widget/types"; interface IDocumentEditor { // document info @@ -47,7 +46,6 @@ interface IDocumentEditor { duplicationConfig?: IDuplicationConfig; pageLockConfig?: IPageLockConfig; pageArchiveConfig?: IPageArchiveConfig; - embedConfig?: IEmbedConfig; } interface DocumentEditorProps extends IDocumentEditor { forwardedRef?: React.Ref; @@ -75,13 +73,11 @@ const DocumentEditor = ({ duplicationConfig, pageLockConfig, pageArchiveConfig, - embedConfig, updatePageTitle, cancelUploadImage, onActionCompleteHandler, rerenderOnPropsChange, }: IDocumentEditor) => { - // const [alert, setAlert] = useState("") const { markings, updateMarkings } = useEditorMarkings(); const [sidePeekVisible, setSidePeekVisible] = useState(true); const router = useRouter(); @@ -112,12 +108,7 @@ const DocumentEditor = ({ cancelUploadImage, rerenderOnPropsChange, forwardedRef, - extensions: DocumentEditorExtensions( - uploadFile, - embedConfig?.issueEmbedConfig, - setIsSubmitting, - setHideDragHandleFunction - ), + extensions: DocumentEditorExtensions(uploadFile, setHideDragHandleFunction, setIsSubmitting), }); if (!editor) { @@ -158,11 +149,11 @@ const DocumentEditor = ({ documentDetails={documentDetails} isSubmitting={isSubmitting} /> -
+
-
+
void; - embedConfig?: IEmbedConfig; } interface DocumentReadOnlyEditorProps extends IDocumentReadOnlyEditor { @@ -51,7 +49,6 @@ const DocumentReadOnlyEditor = ({ pageDuplicationConfig, pageLockConfig, pageArchiveConfig, - embedConfig, rerenderOnPropsChange, onActionCompleteHandler, }: DocumentReadOnlyEditorProps) => { @@ -63,7 +60,7 @@ const DocumentReadOnlyEditor = ({ value, forwardedRef, rerenderOnPropsChange, - extensions: [IssueWidgetExtension({ issueEmbedConfig: embedConfig?.issueEmbedConfig })], + extensions: [IssueWidgetPlaceholder()], }); useEffect(() => { @@ -105,11 +102,11 @@ const DocumentReadOnlyEditor = ({ documentDetails={documentDetails} archivedAt={pageArchiveConfig && pageArchiveConfig.archived_at} /> -
+
-
+
Promise.resolve()} diff --git a/web/hooks/use-issue-embeds.tsx b/web/hooks/use-issue-embeds.tsx deleted file mode 100644 index 2c8f7700b..000000000 --- a/web/hooks/use-issue-embeds.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { TIssue } from "@plane/types"; -import { PROJECT_ISSUES_LIST, STATES_LIST } from "constants/fetch-keys"; -import { EIssuesStoreType } from "constants/issue"; -import { StoreContext } from "contexts/store-context"; -import { autorun, toJS } from "mobx"; -import { useContext } from "react"; -import { IssueService } from "services/issue"; -import useSWR from "swr"; -import { useIssueDetail, useIssues, useMember, useProject, useProjectState } from "./store"; - -const issueService = new IssueService(); - -export const useIssueEmbeds = () => { - const workspaceSlug = useContext(StoreContext).app.router.workspaceSlug; - const projectId = useContext(StoreContext).app.router.projectId; - - const { getProjectById } = useProject(); - const { setPeekIssue } = useIssueDetail(); - const { getStateById } = useProjectState(); - const { getUserDetails } = useMember(); - - const { data: issuesResponse } = useSWR( - workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, - workspaceSlug && projectId ? () => issueService.getIssues(workspaceSlug as string, projectId as string) : null - ); - - const issues = Object.values(issuesResponse ?? {}); - const issuesWithStateAndProject = issues.map((issue) => ({ - ...issue, - state_detail: toJS(getStateById(issue.state_id)), - project_detail: toJS(getProjectById(issue.project_id)), - assignee_details: issue.assignee_ids.map((assigneeid) => toJS(getUserDetails(assigneeid))), - })); - - const fetchIssue = async (issueId: string) => issuesWithStateAndProject.find((issue) => issue.id === issueId); - - const issueWidgetClickAction = (issueId: string) => { - if (!workspaceSlug || !projectId) return; - - setPeekIssue({ workspaceSlug, projectId: projectId, issueId }); - }; - - return { - issues: issuesWithStateAndProject, - fetchIssue, - issueWidgetClickAction, - }; -}; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 4d335f3b8..be512dda0 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -5,7 +5,8 @@ import { useRouter } from "next/router"; import { ReactElement, useEffect, useRef, useState } from "react"; import { Controller, useForm } from "react-hook-form"; // hooks -import { useApplication, useIssues, usePage, useUser, useWorkspace } from "hooks/store"; + +import { useApplication, usePage, useUser, useWorkspace } from "hooks/store"; import useReloadConfirmations from "hooks/use-reload-confirmation"; import useToast from "hooks/use-toast"; // services @@ -27,15 +28,10 @@ import { NextPageWithLayout } from "lib/types"; // constants import { EUserProjectRoles } from "constants/project"; import { useProjectPages } from "hooks/store/use-project-specific-pages"; -import { useIssueEmbeds } from "hooks/use-issue-embeds"; import { IssuePeekOverview } from "components/issues"; -import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; -import { IssueService } from "services/issue"; -import { EIssuesStoreType } from "constants/issue"; // services const fileService = new FileService(); -const issueService = new IssueService(); const PageDetailsPage: NextPageWithLayout = observer(() => { // states @@ -91,8 +87,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { : null ); - const { issues, fetchIssue, issueWidgetClickAction } = useIssueEmbeds(); - const pageStore = usePage(pageId as string); useEffect( @@ -262,7 +256,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { const userCanLock = currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); - return pageIdMobx && issues ? ( + return pageIdMobx ? (
{isPageReadOnly ? ( @@ -291,13 +285,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { } : undefined } - embedConfig={{ - issueEmbedConfig: { - issues: issues, - fetchIssue: fetchIssue, - clickAction: issueWidgetClickAction, - }, - }} /> ) : (
@@ -341,13 +328,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { : undefined } pageLockConfig={userCanLock ? { is_locked: false, action: lockPage } : undefined} - embedConfig={{ - issueEmbedConfig: { - issues: issues, - fetchIssue: fetchIssue, - clickAction: issueWidgetClickAction, - }, - }} /> )} />