mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: Removed Issue Embeds from the Document Editor (#3449)
* fix: issue embed going to next line when selected on slash commands * fix: issue suggestions selecting next embed on arrow down * fix: pages crashing, because of incorrect data type * chore: removed issue embeds from document editor and page interface * fix: upgraded issue widget card to show only Placeholder * fix: pricing url changes for issue embed placeholder * fix: build errors
This commit is contained in:
parent
8d3ea5bb3e
commit
1a1594e818
@ -1,56 +1,29 @@
|
|||||||
import Placeholder from "@tiptap/extension-placeholder";
|
import Placeholder from "@tiptap/extension-placeholder";
|
||||||
import { IssueWidgetExtension } from "src/ui/extensions/widgets/issue-embed-widget";
|
import { IssueWidgetPlaceholder } from "src/ui/extensions/widgets/issue-embed-widget";
|
||||||
|
|
||||||
import { IIssueEmbedConfig } from "src/ui/extensions/widgets/issue-embed-widget/types";
|
|
||||||
|
|
||||||
import { SlashCommand, DragAndDrop } from "@plane/editor-extensions";
|
import { SlashCommand, DragAndDrop } from "@plane/editor-extensions";
|
||||||
import { ISlashCommandItem, UploadImage } from "@plane/editor-core";
|
import { UploadImage } from "@plane/editor-core";
|
||||||
import { IssueSuggestions } from "src/ui/extensions/widgets/issue-embed-suggestion-list";
|
|
||||||
import { LayersIcon } from "@plane/ui";
|
|
||||||
|
|
||||||
export const DocumentEditorExtensions = (
|
export const DocumentEditorExtensions = (
|
||||||
uploadFile: UploadImage,
|
uploadFile: UploadImage,
|
||||||
issueEmbedConfig?: IIssueEmbedConfig,
|
setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void,
|
||||||
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void,
|
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
|
||||||
setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void
|
) => [
|
||||||
) => {
|
SlashCommand(uploadFile, setIsSubmitting),
|
||||||
const additionalOptions: ISlashCommandItem[] = [
|
DragAndDrop(setHideDragHandle),
|
||||||
{
|
Placeholder.configure({
|
||||||
key: "issue_embed",
|
placeholder: ({ node }) => {
|
||||||
title: "Issue embed",
|
if (node.type.name === "heading") {
|
||||||
description: "Embed an issue from the project.",
|
return `Heading ${node.attrs.level}`;
|
||||||
searchTerms: ["issue", "link", "embed"],
|
}
|
||||||
icon: <LayersIcon className="h-3.5 w-3.5" />,
|
if (node.type.name === "image" || node.type.name === "table") {
|
||||||
command: ({ editor, range }) => {
|
return "";
|
||||||
editor
|
}
|
||||||
.chain()
|
|
||||||
.focus()
|
return "Press '/' for commands...";
|
||||||
.insertContentAt(
|
|
||||||
range,
|
|
||||||
"<p class='text-sm bg-gray-300 w-fit pl-3 pr-3 pt-1 pb-1 rounded shadow-sm'>#issue_</p>\n"
|
|
||||||
)
|
|
||||||
.run();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
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 : []),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
@ -78,7 +78,6 @@ const IssueSuggestionList = ({
|
|||||||
const navigationKeys = ["ArrowUp", "ArrowDown", "Enter", "Tab"];
|
const navigationKeys = ["ArrowUp", "ArrowDown", "Enter", "Tab"];
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
if (navigationKeys.includes(e.key)) {
|
if (navigationKeys.includes(e.key)) {
|
||||||
e.preventDefault();
|
|
||||||
// if (editor.isFocused) {
|
// if (editor.isFocused) {
|
||||||
// editor.chain().blur();
|
// editor.chain().blur();
|
||||||
// commandListContainer.current?.focus();
|
// commandListContainer.current?.focus();
|
||||||
@ -87,7 +86,6 @@ const IssueSuggestionList = ({
|
|||||||
setSelectedIndex(
|
setSelectedIndex(
|
||||||
(selectedIndex + displayedItems[currentSection].length - 1) % displayedItems[currentSection].length
|
(selectedIndex + displayedItems[currentSection].length - 1) % displayedItems[currentSection].length
|
||||||
);
|
);
|
||||||
e.stopPropagation();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.key === "ArrowDown") {
|
if (e.key === "ArrowDown") {
|
||||||
@ -102,12 +100,10 @@ const IssueSuggestionList = ({
|
|||||||
[currentSection]: [...prevItems[currentSection], ...nextItems],
|
[currentSection]: [...prevItems[currentSection], ...nextItems],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
e.stopPropagation();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
selectItem(currentSection, selectedIndex);
|
selectItem(currentSection, selectedIndex);
|
||||||
e.stopPropagation();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.key === "Tab") {
|
if (e.key === "Tab") {
|
||||||
@ -115,7 +111,6 @@ const IssueSuggestionList = ({
|
|||||||
const nextSectionIndex = (currentSectionIndex + 1) % sections.length;
|
const nextSectionIndex = (currentSectionIndex + 1) % sections.length;
|
||||||
setCurrentSection(sections[nextSectionIndex]);
|
setCurrentSection(sections[nextSectionIndex]);
|
||||||
setSelectedIndex(0);
|
setSelectedIndex(0);
|
||||||
e.stopPropagation();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -150,7 +145,7 @@ const IssueSuggestionList = ({
|
|||||||
<div
|
<div
|
||||||
id="issue-list-container"
|
id="issue-list-container"
|
||||||
ref={commandListContainer}
|
ref={commandListContainer}
|
||||||
className="fixed z-[10] max-h-80 w-60 overflow-y-auto overflow-x-hidden rounded-md border border-custom-border-100 bg-custom-background-100 px-1 shadow-custom-shadow-xs transition-all"
|
className=" fixed z-[10] max-h-80 w-60 overflow-y-auto overflow-x-hidden rounded-md border border-custom-border-100 bg-custom-background-100 px-1 shadow-custom-shadow-xs transition-all"
|
||||||
>
|
>
|
||||||
{sections.map((section) => {
|
{sections.map((section) => {
|
||||||
const sectionItems = displayedItems[section];
|
const sectionItems = displayedItems[section];
|
||||||
@ -193,29 +188,35 @@ const IssueSuggestionList = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueListRenderer = () => {
|
export const IssueListRenderer = () => {
|
||||||
let component: ReactRenderer | null = null;
|
let component: ReactRenderer | null = null;
|
||||||
let popup: any | null = null;
|
let popup: any | null = null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
|
onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
|
||||||
|
const container = document.querySelector(".frame-renderer") as HTMLElement;
|
||||||
component = new ReactRenderer(IssueSuggestionList, {
|
component = new ReactRenderer(IssueSuggestionList, {
|
||||||
props,
|
props,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @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,
|
getReferenceClientRect: props.clientRect,
|
||||||
appendTo: () => document.querySelector("#editor-container"),
|
|
||||||
content: component.element,
|
content: component.element,
|
||||||
showOnCreate: true,
|
showOnCreate: true,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
trigger: "manual",
|
trigger: "manual",
|
||||||
placement: "bottom-start",
|
placement: "bottom-start",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
container.addEventListener("scroll", () => {
|
||||||
|
popup?.[0].destroy();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onUpdate: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
|
onUpdate: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
|
||||||
component?.updateProps(props);
|
component?.updateProps(props);
|
||||||
@ -230,10 +231,20 @@ export const IssueListRenderer = () => {
|
|||||||
popup?.[0].hide();
|
popup?.[0].hide();
|
||||||
return true;
|
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) => {
|
onExit: (e) => {
|
||||||
|
const container = document.querySelector(".frame-renderer") as HTMLElement;
|
||||||
|
if (container) {
|
||||||
|
container.removeEventListener("scroll", () => {});
|
||||||
|
}
|
||||||
popup?.[0].destroy();
|
popup?.[0].destroy();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
component?.destroy();
|
component?.destroy();
|
||||||
|
@ -1,11 +1,3 @@
|
|||||||
import { IssueWidget } from "src/ui/extensions/widgets/issue-embed-widget/issue-widget-node";
|
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 {
|
export const IssueWidgetPlaceholder = () => IssueWidget.configure({});
|
||||||
issueEmbedConfig?: IIssueEmbedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IssueWidgetExtension = ({ issueEmbedConfig }: IssueWidgetExtensionProps) =>
|
|
||||||
IssueWidget.configure({
|
|
||||||
issueEmbedConfig,
|
|
||||||
});
|
|
||||||
|
@ -1,77 +1,33 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { useState, useEffect } from "react";
|
import { Button } from "@plane/ui";
|
||||||
import { NodeViewWrapper } from "@tiptap/react";
|
import { NodeViewWrapper } from "@tiptap/react";
|
||||||
import { Avatar, AvatarGroup, Loader, PriorityIcon } from "@plane/ui";
|
import { Crown } from "lucide-react";
|
||||||
import { Calendar, AlertTriangle } from "lucide-react";
|
|
||||||
|
|
||||||
export const IssueWidgetCard = (props) => {
|
export const IssueWidgetCard = (props) => (
|
||||||
const [loading, setLoading] = useState<number>(1);
|
<NodeViewWrapper className="issue-embed-component m-2">
|
||||||
const [issueDetails, setIssueDetails] = useState();
|
<div
|
||||||
|
className={`${
|
||||||
useEffect(() => {
|
props.selected ? "border-custom-primary-200 border-[2px]" : ""
|
||||||
props.issueEmbedConfig
|
} w-full h-[100px] cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 shadow-custom-shadow-2xs`}
|
||||||
.fetchIssue(props.node.attrs.entity_identifier)
|
>
|
||||||
.then((issue) => {
|
<h5 className="h-[20%] text-xs text-custom-text-300 p-2">
|
||||||
setIssueDetails(issue);
|
{props.node.attrs.project_identifier}-{props.node.attrs.sequence_id}
|
||||||
setLoading(0);
|
</h5>
|
||||||
})
|
<div className="relative h-[71%]">
|
||||||
.catch(() => {
|
<div className="h-full backdrop-filter backdrop-blur-[30px] bg-custom-background-80 bg-opacity-30 flex items-center w-full justify-between gap-5 mt-2.5 pl-4 pr-5 py-3 max-md:max-w-full max-md:flex-wrap relative">
|
||||||
setLoading(-1);
|
<div className="flex gap-2 items-center">
|
||||||
});
|
<div className="rounded">
|
||||||
}, []);
|
<Crown className="m-2" size={16} color="#FFBA18" />
|
||||||
|
|
||||||
const completeIssueEmbedAction = () => {
|
|
||||||
props.issueEmbedConfig.clickAction(issueDetails.id, props.node.attrs.title);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NodeViewWrapper className="issue-embed-component m-2">
|
|
||||||
{loading == 0 ? (
|
|
||||||
<div
|
|
||||||
onClick={completeIssueEmbedAction}
|
|
||||||
className={`${
|
|
||||||
props.selected ? "border-custom-primary-200 border-[2px]" : ""
|
|
||||||
} w-full cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 p-3 shadow-custom-shadow-2xs`}
|
|
||||||
>
|
|
||||||
<h5 className="text-xs text-custom-text-300">
|
|
||||||
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
|
|
||||||
</h5>
|
|
||||||
<h4 className="break-words text-sm font-medium">{issueDetails.name}</h4>
|
|
||||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
||||||
<div>
|
|
||||||
<PriorityIcon priority={issueDetails.priority} />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="text-custom-text text-sm">
|
||||||
<AvatarGroup size="sm">
|
Embed and access issues in pages seamlessly, upgrade to plane pro now.
|
||||||
{issueDetails.assignee_details.map((assignee) => (
|
|
||||||
<Avatar key={assignee.id} name={assignee.display_name} src={assignee.avatar} className={"m-0"} />
|
|
||||||
))}
|
|
||||||
</AvatarGroup>
|
|
||||||
</div>
|
</div>
|
||||||
{issueDetails.target_date && (
|
|
||||||
<div className="flex h-5 items-center gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs text-custom-text-100">
|
|
||||||
<Calendar className="h-3 w-3" strokeWidth={1.5} />
|
|
||||||
{new Date(issueDetails.target_date).toLocaleDateString()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
|
||||||
|
<Button>Upgrade</Button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
) : loading == -1 ? (
|
</div>
|
||||||
<div className="flex items-center gap-[8px] rounded border-2 border-[#D97706] bg-[#FFFBEB] pb-[10px] pl-[13px] pt-[10px] text-[#D97706]">
|
</div>
|
||||||
<AlertTriangle color={"#D97706"} />
|
</NodeViewWrapper>
|
||||||
{"This Issue embed is not found in any project. It can no longer be updated or accessed from here."}
|
);
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="w-full space-y-2 rounded-md border-[0.5px] border-custom-border-200 p-3 shadow-custom-shadow-2xs">
|
|
||||||
<Loader className={"px-6"}>
|
|
||||||
<Loader.Item height={"30px"} />
|
|
||||||
<div className={"mt-3 space-y-2"}>
|
|
||||||
<Loader.Item height={"20px"} width={"70%"} />
|
|
||||||
<Loader.Item height={"20px"} width={"60%"} />
|
|
||||||
</div>
|
|
||||||
</Loader>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</NodeViewWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -34,9 +34,7 @@ export const IssueWidget = Node.create({
|
|||||||
},
|
},
|
||||||
|
|
||||||
addNodeView() {
|
addNodeView() {
|
||||||
return ReactNodeViewRenderer((props: Object) => (
|
return ReactNodeViewRenderer((props: Object) => <IssueWidgetCard {...props} />);
|
||||||
<IssueWidgetCard {...props} issueEmbedConfig={this.options.issueEmbedConfig} />
|
|
||||||
));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
export interface IEmbedConfig {
|
|
||||||
issueEmbedConfig: IIssueEmbedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IIssueEmbedConfig {
|
|
||||||
fetchIssue: (issueId: string) => Promise<any>;
|
|
||||||
clickAction: (issueId: string, issueTitle: string) => void;
|
|
||||||
issues: Array<any>;
|
|
||||||
}
|
|
@ -10,7 +10,6 @@ import { DocumentDetails } from "src/types/editor-types";
|
|||||||
import { PageRenderer } from "src/ui/components/page-renderer";
|
import { PageRenderer } from "src/ui/components/page-renderer";
|
||||||
import { getMenuOptions } from "src/utils/menu-options";
|
import { getMenuOptions } from "src/utils/menu-options";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { IEmbedConfig } from "src/ui/extensions/widgets/issue-embed-widget/types";
|
|
||||||
|
|
||||||
interface IDocumentEditor {
|
interface IDocumentEditor {
|
||||||
// document info
|
// document info
|
||||||
@ -47,7 +46,6 @@ interface IDocumentEditor {
|
|||||||
duplicationConfig?: IDuplicationConfig;
|
duplicationConfig?: IDuplicationConfig;
|
||||||
pageLockConfig?: IPageLockConfig;
|
pageLockConfig?: IPageLockConfig;
|
||||||
pageArchiveConfig?: IPageArchiveConfig;
|
pageArchiveConfig?: IPageArchiveConfig;
|
||||||
embedConfig?: IEmbedConfig;
|
|
||||||
}
|
}
|
||||||
interface DocumentEditorProps extends IDocumentEditor {
|
interface DocumentEditorProps extends IDocumentEditor {
|
||||||
forwardedRef?: React.Ref<EditorHandle>;
|
forwardedRef?: React.Ref<EditorHandle>;
|
||||||
@ -75,13 +73,11 @@ const DocumentEditor = ({
|
|||||||
duplicationConfig,
|
duplicationConfig,
|
||||||
pageLockConfig,
|
pageLockConfig,
|
||||||
pageArchiveConfig,
|
pageArchiveConfig,
|
||||||
embedConfig,
|
|
||||||
updatePageTitle,
|
updatePageTitle,
|
||||||
cancelUploadImage,
|
cancelUploadImage,
|
||||||
onActionCompleteHandler,
|
onActionCompleteHandler,
|
||||||
rerenderOnPropsChange,
|
rerenderOnPropsChange,
|
||||||
}: IDocumentEditor) => {
|
}: IDocumentEditor) => {
|
||||||
// const [alert, setAlert] = useState<string>("")
|
|
||||||
const { markings, updateMarkings } = useEditorMarkings();
|
const { markings, updateMarkings } = useEditorMarkings();
|
||||||
const [sidePeekVisible, setSidePeekVisible] = useState(true);
|
const [sidePeekVisible, setSidePeekVisible] = useState(true);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -112,12 +108,7 @@ const DocumentEditor = ({
|
|||||||
cancelUploadImage,
|
cancelUploadImage,
|
||||||
rerenderOnPropsChange,
|
rerenderOnPropsChange,
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
extensions: DocumentEditorExtensions(
|
extensions: DocumentEditorExtensions(uploadFile, setHideDragHandleFunction, setIsSubmitting),
|
||||||
uploadFile,
|
|
||||||
embedConfig?.issueEmbedConfig,
|
|
||||||
setIsSubmitting,
|
|
||||||
setHideDragHandleFunction
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
@ -158,11 +149,11 @@ const DocumentEditor = ({
|
|||||||
documentDetails={documentDetails}
|
documentDetails={documentDetails}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<div className="flex h-full w-full overflow-y-auto">
|
<div className="flex h-full w-full overflow-y-auto frame-renderer">
|
||||||
<div className="sticky top-0 h-full w-56 flex-shrink-0 lg:w-72">
|
<div className="sticky top-0 h-full w-56 flex-shrink-0 lg:w-72">
|
||||||
<SummarySideBar editor={editor} markings={markings} sidePeekVisible={sidePeekVisible} />
|
<SummarySideBar editor={editor} markings={markings} sidePeekVisible={sidePeekVisible} />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-[calc(100%-14rem)] lg:w-[calc(100%-18rem-18rem)]">
|
<div className="h-full w-[calc(100%-14rem)] lg:w-[calc(100%-18rem-18rem)] page-renderer">
|
||||||
<PageRenderer
|
<PageRenderer
|
||||||
onActionCompleteHandler={onActionCompleteHandler}
|
onActionCompleteHandler={onActionCompleteHandler}
|
||||||
hideDragHandle={hideDragHandleOnMouseLeave}
|
hideDragHandle={hideDragHandleOnMouseLeave}
|
||||||
|
@ -4,12 +4,11 @@ import { useState, forwardRef, useEffect } from "react";
|
|||||||
import { EditorHeader } from "src/ui/components/editor-header";
|
import { EditorHeader } from "src/ui/components/editor-header";
|
||||||
import { PageRenderer } from "src/ui/components/page-renderer";
|
import { PageRenderer } from "src/ui/components/page-renderer";
|
||||||
import { SummarySideBar } from "src/ui/components/summary-side-bar";
|
import { SummarySideBar } from "src/ui/components/summary-side-bar";
|
||||||
import { IssueWidgetExtension } from "src/ui/extensions/widgets/issue-embed-widget";
|
|
||||||
import { IEmbedConfig } from "src/ui/extensions/widgets/issue-embed-widget/types";
|
|
||||||
import { useEditorMarkings } from "src/hooks/use-editor-markings";
|
import { useEditorMarkings } from "src/hooks/use-editor-markings";
|
||||||
import { DocumentDetails } from "src/types/editor-types";
|
import { DocumentDetails } from "src/types/editor-types";
|
||||||
import { IPageArchiveConfig, IPageLockConfig, IDuplicationConfig } from "src/types/menu-actions";
|
import { IPageArchiveConfig, IPageLockConfig, IDuplicationConfig } from "src/types/menu-actions";
|
||||||
import { getMenuOptions } from "src/utils/menu-options";
|
import { getMenuOptions } from "src/utils/menu-options";
|
||||||
|
import { IssueWidgetPlaceholder } from "../extensions/widgets/issue-embed-widget";
|
||||||
|
|
||||||
interface IDocumentReadOnlyEditor {
|
interface IDocumentReadOnlyEditor {
|
||||||
value: string;
|
value: string;
|
||||||
@ -29,7 +28,6 @@ interface IDocumentReadOnlyEditor {
|
|||||||
message: string;
|
message: string;
|
||||||
type: "success" | "error" | "warning" | "info";
|
type: "success" | "error" | "warning" | "info";
|
||||||
}) => void;
|
}) => void;
|
||||||
embedConfig?: IEmbedConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DocumentReadOnlyEditorProps extends IDocumentReadOnlyEditor {
|
interface DocumentReadOnlyEditorProps extends IDocumentReadOnlyEditor {
|
||||||
@ -51,7 +49,6 @@ const DocumentReadOnlyEditor = ({
|
|||||||
pageDuplicationConfig,
|
pageDuplicationConfig,
|
||||||
pageLockConfig,
|
pageLockConfig,
|
||||||
pageArchiveConfig,
|
pageArchiveConfig,
|
||||||
embedConfig,
|
|
||||||
rerenderOnPropsChange,
|
rerenderOnPropsChange,
|
||||||
onActionCompleteHandler,
|
onActionCompleteHandler,
|
||||||
}: DocumentReadOnlyEditorProps) => {
|
}: DocumentReadOnlyEditorProps) => {
|
||||||
@ -63,7 +60,7 @@ const DocumentReadOnlyEditor = ({
|
|||||||
value,
|
value,
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
rerenderOnPropsChange,
|
rerenderOnPropsChange,
|
||||||
extensions: [IssueWidgetExtension({ issueEmbedConfig: embedConfig?.issueEmbedConfig })],
|
extensions: [IssueWidgetPlaceholder()],
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -105,11 +102,11 @@ const DocumentReadOnlyEditor = ({
|
|||||||
documentDetails={documentDetails}
|
documentDetails={documentDetails}
|
||||||
archivedAt={pageArchiveConfig && pageArchiveConfig.archived_at}
|
archivedAt={pageArchiveConfig && pageArchiveConfig.archived_at}
|
||||||
/>
|
/>
|
||||||
<div className="flex h-full w-full overflow-y-auto">
|
<div className="flex h-full w-full overflow-y-auto frame-renderer">
|
||||||
<div className="sticky top-0 h-full w-56 flex-shrink-0 lg:w-80">
|
<div className="sticky top-0 h-full w-56 flex-shrink-0 lg:w-80">
|
||||||
<SummarySideBar editor={editor} markings={markings} sidePeekVisible={sidePeekVisible} />
|
<SummarySideBar editor={editor} markings={markings} sidePeekVisible={sidePeekVisible} />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-[calc(100%-14rem)] lg:w-[calc(100%-18rem-18rem)]">
|
<div className="h-full w-[calc(100%-14rem)] lg:w-[calc(100%-18rem-18rem)] page-renderer">
|
||||||
<PageRenderer
|
<PageRenderer
|
||||||
onActionCompleteHandler={onActionCompleteHandler}
|
onActionCompleteHandler={onActionCompleteHandler}
|
||||||
updatePageTitle={() => Promise.resolve()}
|
updatePageTitle={() => Promise.resolve()}
|
||||||
|
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
@ -5,7 +5,8 @@ import { useRouter } from "next/router";
|
|||||||
import { ReactElement, useEffect, useRef, useState } from "react";
|
import { ReactElement, useEffect, useRef, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// hooks
|
// 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 useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
@ -27,15 +28,10 @@ import { NextPageWithLayout } from "lib/types";
|
|||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||||
import { useIssueEmbeds } from "hooks/use-issue-embeds";
|
|
||||||
import { IssuePeekOverview } from "components/issues";
|
import { IssuePeekOverview } from "components/issues";
|
||||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
|
||||||
import { IssueService } from "services/issue";
|
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
|
||||||
|
|
||||||
// services
|
// services
|
||||||
const fileService = new FileService();
|
const fileService = new FileService();
|
||||||
const issueService = new IssueService();
|
|
||||||
|
|
||||||
const PageDetailsPage: NextPageWithLayout = observer(() => {
|
const PageDetailsPage: NextPageWithLayout = observer(() => {
|
||||||
// states
|
// states
|
||||||
@ -91,8 +87,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { issues, fetchIssue, issueWidgetClickAction } = useIssueEmbeds();
|
|
||||||
|
|
||||||
const pageStore = usePage(pageId as string);
|
const pageStore = usePage(pageId as string);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
@ -262,7 +256,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
const userCanLock =
|
const userCanLock =
|
||||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||||
|
|
||||||
return pageIdMobx && issues ? (
|
return pageIdMobx ? (
|
||||||
<div className="flex h-full flex-col justify-between">
|
<div className="flex h-full flex-col justify-between">
|
||||||
<div className="h-full w-full overflow-hidden">
|
<div className="h-full w-full overflow-hidden">
|
||||||
{isPageReadOnly ? (
|
{isPageReadOnly ? (
|
||||||
@ -291,13 +285,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
embedConfig={{
|
|
||||||
issueEmbedConfig: {
|
|
||||||
issues: issues,
|
|
||||||
fetchIssue: fetchIssue,
|
|
||||||
clickAction: issueWidgetClickAction,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative h-full w-full overflow-hidden">
|
<div className="relative h-full w-full overflow-hidden">
|
||||||
@ -341,13 +328,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
pageLockConfig={userCanLock ? { is_locked: false, action: lockPage } : undefined}
|
pageLockConfig={userCanLock ? { is_locked: false, action: lockPage } : undefined}
|
||||||
embedConfig={{
|
|
||||||
issueEmbedConfig: {
|
|
||||||
issues: issues,
|
|
||||||
fetchIssue: fetchIssue,
|
|
||||||
clickAction: issueWidgetClickAction,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user