refactor: create separate hook to handle page description

This commit is contained in:
Aaryan Khandelwal 2024-05-15 18:07:13 +05:30
parent 8c46a54e9e
commit 4579a947c6
4 changed files with 77 additions and 42 deletions

View File

@ -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<Props> = 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<Props> = 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<Props> = 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 ?? "<p></p>");
const yDocBinaryString = proseMirrorJSONToBinaryString(contentJSON, "default", editorSchema);
updateDescription(yDocBinaryString, pageDescription ?? "<p></p>").then(async () => {
await mutateDescriptionYJS();
setIsDescriptionReady(true);
});
} else setIsDescriptionReady(true);
}, [mutateDescriptionYJS, pageDescription, pageDescriptionYJS, updateDescription]);
useEffect(() => {
updateMarkings(pageDescription ?? "<p></p>");
}, [pageDescription, updateMarkings]);

View File

@ -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 ?? "<p></p>");
const yDocBinaryString = proseMirrorJSONToBinaryString(contentJSON, "default", editorSchema);
updateDescription(yDocBinaryString, pageDescription ?? "<p></p>").then(async () => {
await mutateDescriptionYJS();
setIsDescriptionReady(true);
});
} else setIsDescriptionReady(true);
}, [mutateDescriptionYJS, pageDescription, pageDescriptionYJS, updateDescription]);
return { isDescriptionReady, pageDescriptionYJS };
};

View File

@ -133,7 +133,15 @@ export class PageService extends APIService {
});
}
async updateDescriptionYJS(workspaceSlug: string, projectId: string, pageId: string, data: any): Promise<any> {
async updateDescriptionYJS(
workspaceSlug: string,
projectId: string,
pageId: string,
data: {
description_binary: string;
description_html: string;
}
): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, data)
.then((response) => response?.data)
.catch((error) => {

View File

@ -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) {