diff --git a/web/components/pages/editor/editor-body.tsx b/web/components/pages/editor/editor-body.tsx index 60f167c93..746bfedd0 100644 --- a/web/components/pages/editor/editor-body.tsx +++ b/web/components/pages/editor/editor-body.tsx @@ -1,17 +1,14 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; -import useSWR from "swr"; -// editor +// document-editor import { DocumentEditorWithRef, DocumentReadOnlyEditorWithRef, EditorReadOnlyRefApi, EditorRefApi, IMarking, - proseMirrorJSONToBinaryString, } from "@plane/document-editor"; -import { generateJSONfromHTML } from "@plane/editor-core"; // types import { IUserLite } from "@plane/types"; // components @@ -20,12 +17,11 @@ import { PageContentBrowser, PageContentLoader, PageEditorTitle } from "@/compon import { cn } from "@/helpers/common.helper"; // hooks import { useMember, useMention, useUser, useWorkspace } from "@/hooks/store"; +import { usePageDescription } from "@/hooks/use-page-description"; import { usePageFilters } from "@/hooks/use-page-filters"; import useReloadConfirmations from "@/hooks/use-reload-confirmation"; // services import { FileService } from "@/services/file.service"; -import { PageService } from "@/services/page.service"; -const pageService = new PageService(); // store import { IPageStore } from "@/store/pages/page.store"; @@ -55,8 +51,6 @@ export const PageEditorBody: React.FC = observer((props) => { sidePeekVisible, updateMarkings, } = props; - // states - const [isDescriptionReady, setIsDescriptionReady] = useState(false); // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -72,9 +66,15 @@ export const PageEditorBody: React.FC = observer((props) => { const pageId = pageStore?.id; const pageTitle = pageStore?.name ?? ""; const pageDescription = pageStore?.description_html; - const { isContentEditable, isSubmitting, updateTitle, updateDescription, setIsSubmitting } = pageStore; + const { isContentEditable, isSubmitting, updateTitle, setIsSubmitting } = pageStore; const projectMemberIds = projectId ? getProjectMemberIds(projectId.toString()) : []; const projectMemberDetails = projectMemberIds?.map((id) => getUserDetails(id) as IUserLite); + // project-description + const { isDescriptionReady, pageDescriptionYJS } = usePageDescription({ + pageStore, + projectId, + workspaceSlug, + }); // use-mention const { mentionHighlights, mentionSuggestions } = useMention({ workspaceSlug: workspaceSlug?.toString() ?? "", @@ -87,36 +87,6 @@ export const PageEditorBody: React.FC = observer((props) => { useReloadConfirmations(isSubmitting === "submitting"); - const { data: descriptionYJS, mutate: mutateDescriptionYJS } = useSWR( - workspaceSlug && projectId && pageId ? `PAGE_DESCRIPTION_${workspaceSlug}_${projectId}_${pageId}` : null, - workspaceSlug && projectId && pageId - ? () => pageService.fetchDescriptionYJS(workspaceSlug.toString(), projectId.toString(), pageId.toString()) - : null, - { - refreshInterval: 15000, - revalidateOnFocus: true, - revalidateOnReconnect: true, - } - ); - const pageDescriptionYJS = useMemo( - () => (descriptionYJS ? new Uint8Array(descriptionYJS) : undefined), - [descriptionYJS] - ); - - // if description_yjs field is empty, convert description_html to yDoc and update the DB - // TODO: this is a one-time operation, and needs to be removed once all the pages are updated - useEffect(() => { - if (!pageDescriptionYJS || !pageDescription) return; - if (pageDescriptionYJS.byteLength === 0) { - const { contentJSON, editorSchema } = generateJSONfromHTML(pageDescription ?? "

"); - const yDocBinaryString = proseMirrorJSONToBinaryString(contentJSON, "default", editorSchema); - updateDescription(yDocBinaryString, pageDescription ?? "

").then(async () => { - await mutateDescriptionYJS(); - setIsDescriptionReady(true); - }); - } else setIsDescriptionReady(true); - }, [mutateDescriptionYJS, pageDescription, pageDescriptionYJS, updateDescription]); - useEffect(() => { updateMarkings(pageDescription ?? "

"); }, [pageDescription, updateMarkings]); diff --git a/web/hooks/use-page-description.ts b/web/hooks/use-page-description.ts new file mode 100644 index 000000000..2e7ba5c01 --- /dev/null +++ b/web/hooks/use-page-description.ts @@ -0,0 +1,57 @@ +import { useEffect, useMemo, useState } from "react"; +import useSWR from "swr"; +// editor +import { proseMirrorJSONToBinaryString } from "@plane/document-editor"; +import { generateJSONfromHTML } from "@plane/editor-core"; +// services +import { PageService } from "@/services/page.service"; +import { IPageStore } from "@/store/pages/page.store"; +const pageService = new PageService(); + +type Props = { + pageStore: IPageStore; + projectId: string | string[] | undefined; + workspaceSlug: string | string[] | undefined; +}; + +export const usePageDescription = (props: Props) => { + const { pageStore, projectId, workspaceSlug } = props; + // states + const [isDescriptionReady, setIsDescriptionReady] = useState(false); + // derived values + const { updateDescription } = pageStore; + const pageDescription = pageStore.description_html; + const pageId = pageStore.id; + + const { data: descriptionYJS, mutate: mutateDescriptionYJS } = useSWR( + workspaceSlug && projectId && pageId ? `PAGE_DESCRIPTION_${workspaceSlug}_${projectId}_${pageId}` : null, + workspaceSlug && projectId && pageId + ? () => pageService.fetchDescriptionYJS(workspaceSlug.toString(), projectId.toString(), pageId.toString()) + : null, + { + refreshInterval: 15000, + revalidateOnFocus: true, + revalidateOnReconnect: true, + } + ); + const pageDescriptionYJS = useMemo( + () => (descriptionYJS ? new Uint8Array(descriptionYJS) : undefined), + [descriptionYJS] + ); + + // if description_binary field is empty, convert description_html to yDoc and update the DB + // TODO: this is a one-time operation, and needs to be removed once all the pages are updated + useEffect(() => { + if (!pageDescriptionYJS || !pageDescription) return; + if (pageDescriptionYJS.byteLength === 0) { + const { contentJSON, editorSchema } = generateJSONfromHTML(pageDescription ?? "

"); + const yDocBinaryString = proseMirrorJSONToBinaryString(contentJSON, "default", editorSchema); + updateDescription(yDocBinaryString, pageDescription ?? "

").then(async () => { + await mutateDescriptionYJS(); + setIsDescriptionReady(true); + }); + } else setIsDescriptionReady(true); + }, [mutateDescriptionYJS, pageDescription, pageDescriptionYJS, updateDescription]); + + return { isDescriptionReady, pageDescriptionYJS }; +}; diff --git a/web/services/page.service.ts b/web/services/page.service.ts index aa898e1fa..0b0d39f24 100644 --- a/web/services/page.service.ts +++ b/web/services/page.service.ts @@ -133,7 +133,15 @@ export class PageService extends APIService { }); } - async updateDescriptionYJS(workspaceSlug: string, projectId: string, pageId: string, data: any): Promise { + async updateDescriptionYJS( + workspaceSlug: string, + projectId: string, + pageId: string, + data: { + description_binary: string; + description_html: string; + } + ): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, data) .then((response) => response?.data) .catch((error) => { diff --git a/web/store/pages/page.store.ts b/web/store/pages/page.store.ts index de88beafc..36c6fa300 100644 --- a/web/store/pages/page.store.ts +++ b/web/store/pages/page.store.ts @@ -330,7 +330,7 @@ export class PageStore implements IPageStore { try { await this.pageService.updateDescriptionYJS(workspaceSlug, projectId, this.id, { - description_yjs: binaryString, + description_binary: binaryString, description_html: descriptionHTML, }); } catch (error) {