[ FEATURE ] New Issue Widget for displaying issues inside document-editor (#2920)
* feat: added heading 3 in the editor summary markings * feat: fixed editor and summary bar sizing * feat: added `issue-embed` extension * feat: exposed issue embed extension * feat: added main embed config configuration to document editor body * feat: added peek overview and issue embed fetch function * feat: enabled slash commands to take additonal suggestions from editors * chore: replaced `IssueEmbedWidget` into widget extension * chore: removed issue embed from previous places * feat: added issue embed suggestion extension * feat: added issue embed suggestion renderer * feat: added issue embed suggestions into extensions module * feat: added issues in issueEmbedConfiguration in document editor * chore: package fixes * chore: removed log statements * feat: added title updation logic into document editor * fix: issue suggestion items, not rendering issue widget on enter * feat: added error card for issue widget * feat: improved focus logic for issue search and navigate * feat: appended transactionid for issueWidgetTransaction * chore: packages update * feat: disabled editing of title in readonly mode * feat: added issueEmbedConfig in readonly editor * fix: issue suggestions not loading after structure changed to object * feat: added toast messages for success/error messages from doc editor * fix: issue suggestions sorting issue * fix: formatting errors resolved * fix: infinite reloading of the readonly document editor * fix: css in avatar of issue widget card * feat: added show alert on pages reload * feat: added saving state for the pages editor * fix: issue with heading 3 in side bar view * style: updated issue suggestions dropdown ui * fix: Pages intiliazation and mutation with updated MobX store * fixed image uploads being cancelled on refocus due to swr * fix: issue with same description rerendering empty content fixed * fix: scroll in issue suggestion view * fix: added submission prop * fix: Updated the comment update to take issue id in inbox issues * feat:changed date representation in IssueEmbedCard * fix: page details mutation with optimistic updates using swr * fix: menu options in read only editor with auth fixed * fix: add error handling for title and page desc * fixed yarn.lock * fix: read-only editor title wrapping * fix: build error with rich text editor --------- Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> Co-authored-by: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com>
This commit is contained in:
parent
95c7403efc
commit
b5ac2f8078
33 changed files with 1412 additions and 381 deletions
|
|
@ -1,9 +1,7 @@
|
|||
import React, { useEffect, useRef, useState, ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import useSWR, { MutatorOptions } from "swr";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Sparkle } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// services
|
||||
import { PageService } from "services/page.service";
|
||||
import { FileService } from "services/file.service";
|
||||
|
|
@ -16,7 +14,6 @@ import { AppLayout } from "layouts/app-layout";
|
|||
// components
|
||||
import { PageDetailsHeader } from "components/headers/page-details";
|
||||
import { EmptyState } from "components/common";
|
||||
import { GptAssistantModal } from "components/core";
|
||||
// ui
|
||||
import { DocumentEditorWithRef, DocumentReadOnlyEditorWithRef } from "@plane/document-editor";
|
||||
import { Spinner } from "@plane/ui";
|
||||
|
|
@ -26,158 +23,52 @@ import emptyPage from "public/empty-state/page.svg";
|
|||
import { renderDateFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { NextPageWithLayout } from "types/app";
|
||||
import { IPage } from "types";
|
||||
import { IPage, IIssue } from "types";
|
||||
// fetch-keys
|
||||
import { PAGE_DETAILS } from "constants/fetch-keys";
|
||||
import { PAGE_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
||||
import { IssueService } from "services/issue";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { GptAssistantModal } from "components/core";
|
||||
import { Sparkle } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// services
|
||||
const fileService = new FileService();
|
||||
const pageService = new PageService();
|
||||
const issueService = new IssueService();
|
||||
|
||||
const PageDetailsPage: NextPageWithLayout = observer(() => {
|
||||
const editorRef = useRef<any>(null);
|
||||
// states
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
||||
const [gptModalOpen, setGptModal] = useState(false);
|
||||
// store
|
||||
const {
|
||||
projectIssues: { updateIssue },
|
||||
appConfig: { envConfig },
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
// router
|
||||
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
||||
const [gptModalOpen, setGptModal] = useState(false);
|
||||
|
||||
const { setShowAlert } = useReloadConfirmations();
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, pageId } = router.query;
|
||||
const { workspaceSlug, projectId, pageId, peekIssueId } = router.query;
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { user } = useUser();
|
||||
|
||||
const { handleSubmit, reset, getValues, control, setValue, watch } = useForm<IPage>({
|
||||
defaultValues: { name: "", description_html: "<p></p>" },
|
||||
const { handleSubmit, reset, setValue, watch, getValues, control } = useForm<IPage>({
|
||||
defaultValues: { name: "", description_html: "" },
|
||||
});
|
||||
|
||||
// =================== Fetching Page Details ======================
|
||||
const {
|
||||
data: pageDetails,
|
||||
mutate: mutatePageDetails,
|
||||
error,
|
||||
} = useSWR(
|
||||
workspaceSlug && projectId && pageId ? PAGE_DETAILS(pageId.toString()) : null,
|
||||
workspaceSlug && projectId && pageId
|
||||
? () => pageService.getPageDetails(workspaceSlug.toString(), projectId.toString(), pageId.toString())
|
||||
: null
|
||||
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 updatePage = async (formData: IPage) => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
if (!formData.name || formData.name.length === 0 || formData.name === "") return;
|
||||
|
||||
await pageService
|
||||
.patchPage(workspaceSlug.toString(), projectId.toString(), pageId.toString(), formData)
|
||||
.then(() => {
|
||||
mutatePageDetails(
|
||||
(prevData) => ({
|
||||
...prevData,
|
||||
...formData,
|
||||
}),
|
||||
false
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const createPage = async (payload: Partial<IPage>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
await pageService.createPage(workspaceSlug.toString(), projectId.toString(), payload);
|
||||
};
|
||||
|
||||
// ================ Page Menu Actions ==================
|
||||
const duplicate_page = async () => {
|
||||
const currentPageValues = getValues();
|
||||
const formData: Partial<IPage> = {
|
||||
name: "Copy of " + currentPageValues.name,
|
||||
description_html: currentPageValues.description_html,
|
||||
};
|
||||
await createPage(formData);
|
||||
};
|
||||
|
||||
const archivePage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
try {
|
||||
mutatePageDetails((prevData) => {
|
||||
if (!prevData) return;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
archived_at: renderDateFormat(new Date()),
|
||||
};
|
||||
}, true);
|
||||
|
||||
await pageService.archivePage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
|
||||
} catch (e) {
|
||||
mutatePageDetails();
|
||||
}
|
||||
};
|
||||
|
||||
const unArchivePage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
try {
|
||||
mutatePageDetails((prevData) => {
|
||||
if (!prevData) return;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
archived_at: null,
|
||||
};
|
||||
}, false);
|
||||
|
||||
await pageService.restorePage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
|
||||
} catch (e) {
|
||||
mutatePageDetails();
|
||||
}
|
||||
};
|
||||
|
||||
// ========================= Page Lock ==========================
|
||||
const lockPage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
try {
|
||||
mutatePageDetails((prevData) => {
|
||||
if (!prevData) return;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
is_locked: true,
|
||||
};
|
||||
}, false);
|
||||
|
||||
await pageService.lockPage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
|
||||
} catch (e) {
|
||||
mutatePageDetails();
|
||||
}
|
||||
};
|
||||
|
||||
const unlockPage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
try {
|
||||
mutatePageDetails((prevData) => {
|
||||
if (!prevData) return;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
is_locked: false,
|
||||
};
|
||||
}, false);
|
||||
|
||||
await pageService.unlockPage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
|
||||
} catch (e) {
|
||||
mutatePageDetails();
|
||||
}
|
||||
};
|
||||
const issues = Object.values(issuesResponse ?? {});
|
||||
|
||||
const handleAiAssistance = async (response: string) => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
|
@ -195,13 +86,245 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!pageDetails) return;
|
||||
// =================== Fetching Page Details ======================
|
||||
const {
|
||||
data: pageDetails,
|
||||
mutate: mutatePageDetails,
|
||||
error,
|
||||
} = useSWR(
|
||||
workspaceSlug && projectId && pageId ? PAGE_DETAILS(pageId.toString()) : null,
|
||||
workspaceSlug && projectId && pageId
|
||||
? () => pageService.getPageDetails(workspaceSlug.toString(), projectId.toString(), pageId.toString())
|
||||
: null,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
}
|
||||
);
|
||||
|
||||
reset({
|
||||
...pageDetails,
|
||||
const handleUpdateIssue = (issueId: string, data: Partial<IIssue>) => {
|
||||
if (!workspaceSlug || !projectId || !user) return;
|
||||
|
||||
updateIssue(workspaceSlug.toString(), projectId.toString(), issueId, data);
|
||||
};
|
||||
|
||||
const fetchIssue = async (issueId: string) => {
|
||||
const issue = await issueService.retrieve(workspaceSlug as string, projectId as string, issueId as string);
|
||||
return issue as IIssue;
|
||||
};
|
||||
|
||||
const issueWidgetClickAction = (issueId: string, issueTitle: string) => {
|
||||
const url = new URL(router.asPath, window.location.origin);
|
||||
const params = new URLSearchParams(url.search);
|
||||
|
||||
if (params.has("peekIssueId")) {
|
||||
params.set("peekIssueId", issueId);
|
||||
} else {
|
||||
params.append("peekIssueId", issueId);
|
||||
}
|
||||
// Replace the current URL with the new one
|
||||
router.replace(`${url.pathname}?${params.toString()}`, undefined, { shallow: true });
|
||||
};
|
||||
|
||||
const actionCompleteAlert = ({
|
||||
title,
|
||||
message,
|
||||
type,
|
||||
}: {
|
||||
title: string;
|
||||
message: string;
|
||||
type: "success" | "error" | "warning" | "info";
|
||||
}) => {
|
||||
setToastAlert({
|
||||
title,
|
||||
message,
|
||||
type,
|
||||
});
|
||||
}, [reset, pageDetails]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting === "submitted") {
|
||||
setShowAlert(false);
|
||||
setTimeout(async () => {
|
||||
setIsSubmitting("saved");
|
||||
}, 2000);
|
||||
} else if (isSubmitting === "submitting") {
|
||||
setShowAlert(true);
|
||||
}
|
||||
}, [isSubmitting, setShowAlert]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pageDetails?.description_html) {
|
||||
setLocalIssueDescription({ id: pageId as string, description_html: pageDetails.description_html });
|
||||
}
|
||||
}, [pageDetails?.description_html]);
|
||||
|
||||
function createObjectFromArray(keys: string[], options: any): any {
|
||||
return keys.reduce((obj, key) => {
|
||||
if (options[key] !== undefined) {
|
||||
obj[key] = options[key];
|
||||
}
|
||||
return obj;
|
||||
}, {} as { [key: string]: any });
|
||||
}
|
||||
|
||||
const mutatePageDetailsHelper = (
|
||||
serverMutatorFn: Promise<any>,
|
||||
dataToMutate: Partial<IPage>,
|
||||
formDataValues: Array<keyof IPage>,
|
||||
onErrorAction: () => void
|
||||
) => {
|
||||
const commonSwrOptions: MutatorOptions = {
|
||||
revalidate: true,
|
||||
populateCache: false,
|
||||
rollbackOnError: (e) => {
|
||||
onErrorAction();
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const formData = getValues();
|
||||
const formDataMutationObject = createObjectFromArray(formDataValues, formData);
|
||||
|
||||
mutatePageDetails(async () => serverMutatorFn, {
|
||||
optimisticData: (prevData) => {
|
||||
if (!prevData) return;
|
||||
return {
|
||||
...prevData,
|
||||
description_html: formData["description_html"],
|
||||
...formDataMutationObject,
|
||||
...dataToMutate,
|
||||
};
|
||||
},
|
||||
...commonSwrOptions,
|
||||
});
|
||||
};
|
||||
|
||||
const updatePage = async (formData: IPage) => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
formData.name = pageDetails?.name as string;
|
||||
|
||||
if (!formData?.name || formData?.name.length === 0) return;
|
||||
|
||||
try {
|
||||
await pageService.patchPage(workspaceSlug.toString(), projectId.toString(), pageId.toString(), formData);
|
||||
} catch (error) {
|
||||
actionCompleteAlert({
|
||||
title: `Page could not be updated`,
|
||||
message: `Sorry, page could not be updated, please try again later`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updatePageTitle = async (title: string) => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
mutatePageDetailsHelper(
|
||||
pageService.patchPage(workspaceSlug.toString(), projectId.toString(), pageId.toString(), { name: title }),
|
||||
{
|
||||
name: title,
|
||||
},
|
||||
[],
|
||||
() =>
|
||||
actionCompleteAlert({
|
||||
title: `Page Title could not be updated`,
|
||||
message: `Sorry, page title could not be updated, please try again later`,
|
||||
type: "error",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const createPage = async (payload: Partial<IPage>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
await pageService.createPage(workspaceSlug.toString(), projectId.toString(), payload);
|
||||
};
|
||||
|
||||
// ================ Page Menu Actions ==================
|
||||
const duplicate_page = async () => {
|
||||
const currentPageValues = getValues();
|
||||
const formData: Partial<IPage> = {
|
||||
name: "Copy of " + pageDetails?.name,
|
||||
description_html: currentPageValues.description_html,
|
||||
};
|
||||
await createPage(formData);
|
||||
};
|
||||
|
||||
const archivePage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
mutatePageDetailsHelper(
|
||||
pageService.archivePage(workspaceSlug.toString(), projectId.toString(), pageId.toString()),
|
||||
{
|
||||
archived_at: renderDateFormat(new Date()),
|
||||
},
|
||||
["description_html"],
|
||||
() =>
|
||||
actionCompleteAlert({
|
||||
title: `Page could not be Archived`,
|
||||
message: `Sorry, page could not be Archived, please try again later`,
|
||||
type: "error",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const unArchivePage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
mutatePageDetailsHelper(
|
||||
pageService.restorePage(workspaceSlug.toString(), projectId.toString(), pageId.toString()),
|
||||
{
|
||||
archived_at: null,
|
||||
},
|
||||
["description_html"],
|
||||
() =>
|
||||
actionCompleteAlert({
|
||||
title: `Page could not be Restored`,
|
||||
message: `Sorry, page could not be Restored, please try again later`,
|
||||
type: "error",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// ========================= Page Lock ==========================
|
||||
const lockPage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
mutatePageDetailsHelper(
|
||||
pageService.lockPage(workspaceSlug.toString(), projectId.toString(), pageId.toString()),
|
||||
{
|
||||
is_locked: true,
|
||||
},
|
||||
["description_html"],
|
||||
() =>
|
||||
actionCompleteAlert({
|
||||
title: `Page cannot be Locked`,
|
||||
message: `Sorry, page cannot be Locked, please try again later`,
|
||||
type: "error",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const unlockPage = async () => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
||||
mutatePageDetailsHelper(
|
||||
pageService.unlockPage(workspaceSlug.toString(), projectId.toString(), pageId.toString()),
|
||||
{
|
||||
is_locked: false,
|
||||
},
|
||||
["description_html"],
|
||||
() =>
|
||||
actionCompleteAlert({
|
||||
title: `Page could not be Unlocked`,
|
||||
message: `Sorry, page could not be Unlocked, please try again later`,
|
||||
type: "error",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const [localPageDescription, setLocalIssueDescription] = useState({
|
||||
id: pageId as string,
|
||||
description_html: "",
|
||||
});
|
||||
|
||||
const debouncedFormSave = debounce(async () => {
|
||||
handleSubmit(updatePage)().finally(() => setIsSubmitting("submitted"));
|
||||
|
|
@ -222,6 +345,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
|
||||
const isPageReadOnly =
|
||||
pageDetails?.is_locked ||
|
||||
pageDetails?.archived_at ||
|
||||
(currentProjectRole && [EUserWorkspaceRoles.VIEWER, EUserWorkspaceRoles.GUEST].includes(currentProjectRole));
|
||||
|
||||
const isCurrentUserOwner = pageDetails?.owned_by === user?.id;
|
||||
|
|
@ -234,14 +358,16 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{pageDetails ? (
|
||||
{pageDetails && issuesResponse ? (
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
{isPageReadOnly ? (
|
||||
<DocumentReadOnlyEditorWithRef
|
||||
onActionCompleteHandler={actionCompleteAlert}
|
||||
ref={editorRef}
|
||||
value={pageDetails.description_html}
|
||||
customClassName={"tracking-tight self-center w-full max-w-full px-0"}
|
||||
value={localPageDescription.description_html}
|
||||
rerenderOnPropsChange={localPageDescription}
|
||||
customClassName={"tracking-tight w-full px-0"}
|
||||
borderOnFocus={false}
|
||||
noBorder
|
||||
documentDetails={{
|
||||
|
|
@ -252,12 +378,15 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
last_updated_by: pageDetails.updated_by,
|
||||
}}
|
||||
pageLockConfig={
|
||||
!pageDetails.archived_at && user && pageDetails.owned_by === user.id
|
||||
userCanLock && !pageDetails.archived_at
|
||||
? { action: unlockPage, is_locked: pageDetails.is_locked }
|
||||
: undefined
|
||||
}
|
||||
pageDuplicationConfig={
|
||||
userCanDuplicate && !pageDetails.archived_at ? { action: duplicate_page } : undefined
|
||||
}
|
||||
pageArchiveConfig={
|
||||
user && pageDetails.owned_by === user.id
|
||||
userCanArchive
|
||||
? {
|
||||
action: pageDetails.archived_at ? unArchivePage : archivePage,
|
||||
is_archived: pageDetails.archived_at ? true : false,
|
||||
|
|
@ -265,6 +394,13 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
}
|
||||
: undefined
|
||||
}
|
||||
embedConfig={{
|
||||
issueEmbedConfig: {
|
||||
issues: issues,
|
||||
fetchIssue: fetchIssue,
|
||||
clickAction: issueWidgetClickAction,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full relative overflow-hidden">
|
||||
|
|
@ -273,6 +409,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<DocumentEditorWithRef
|
||||
isSubmitting={isSubmitting}
|
||||
documentDetails={{
|
||||
title: pageDetails.name,
|
||||
created_by: pageDetails.created_by,
|
||||
|
|
@ -281,14 +418,20 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
last_updated_by: pageDetails.updated_by,
|
||||
}}
|
||||
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
|
||||
setShouldShowAlert={setShowAlert}
|
||||
deleteFile={fileService.deleteImage}
|
||||
restoreFile={fileService.restoreImage}
|
||||
cancelUploadImage={fileService.cancelUpload}
|
||||
ref={editorRef}
|
||||
debouncedUpdatesEnabled={false}
|
||||
setIsSubmitting={setIsSubmitting}
|
||||
value={!value || value === "" ? "<p></p>" : value}
|
||||
updatePageTitle={updatePageTitle}
|
||||
value={localPageDescription.description_html}
|
||||
rerenderOnPropsChange={localPageDescription}
|
||||
onActionCompleteHandler={actionCompleteAlert}
|
||||
customClassName="tracking-tight self-center px-0 h-full w-full"
|
||||
onChange={(_description_json: Object, description_html: string) => {
|
||||
setShowAlert(true);
|
||||
onChange(description_html);
|
||||
setIsSubmitting("submitting");
|
||||
debouncedFormSave();
|
||||
|
|
@ -303,6 +446,13 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
: undefined
|
||||
}
|
||||
pageLockConfig={userCanLock ? { is_locked: false, action: lockPage } : undefined}
|
||||
embedConfig={{
|
||||
issueEmbedConfig: {
|
||||
issues: issues,
|
||||
fetchIssue: fetchIssue,
|
||||
clickAction: issueWidgetClickAction,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -310,7 +460,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 absolute top-3 right-[68px]"
|
||||
className="flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 absolute top-2.5 right-[68px]"
|
||||
onClick={() => setGptModal((prevData) => !prevData)}
|
||||
>
|
||||
<Sparkle className="h-4 w-4" />
|
||||
|
|
@ -332,6 +482,17 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
<IssuePeekOverview
|
||||
workspaceSlug={workspaceSlug as string}
|
||||
projectId={projectId as string}
|
||||
issueId={peekIssueId ? (peekIssueId as string) : ""}
|
||||
isArchived={false}
|
||||
handleIssue={(issueToUpdate) => {
|
||||
if (peekIssueId && typeof peekIssueId === "string") {
|
||||
handleUpdateIssue(peekIssueId, issueToUpdate);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue