[PE-92] fix: removing readonly collaborative document editor (#6209)

* fix: removing readonly editor

* fix: sync state

* fix: indexeddb sync loader added

* fix: remove node error fixed

* style: page title and checkbox

* chore: removing the syncing logic

* revert: is editable check removed in display message

* fix: editable field optional

* fix: editable removed as optional prop

* fix: extra options import fix

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
M. Palanikannan 2024-12-18 12:58:18 +05:30 committed by GitHub
parent 580c4b1930
commit e33bae2125
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 215 additions and 460 deletions

View file

@ -1,11 +1,9 @@
import { useCallback, useMemo } from "react";
import { Dispatch, SetStateAction, useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// document-editor
import {
CollaborativeDocumentEditorWithRef,
CollaborativeDocumentReadOnlyEditorWithRef,
EditorReadOnlyRefApi,
EditorRefApi,
TAIMenuProps,
TDisplayConfig,
@ -20,7 +18,7 @@ import { Row } from "@plane/ui";
import { PageContentBrowser, PageContentLoader, PageEditorTitle } from "@/components/pages";
// helpers
import { cn, LIVE_BASE_PATH, LIVE_BASE_URL } from "@/helpers/common.helper";
import { getEditorFileHandlers, getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
import { getEditorFileHandlers } from "@/helpers/editor.helper";
import { generateRandomColor } from "@/helpers/string.helper";
// hooks
import { useMember, useMention, useUser, useWorkspace } from "@/hooks/store";
@ -42,24 +40,14 @@ const fileService = new FileService();
type Props = {
editorRef: React.RefObject<EditorRefApi>;
editorReady: boolean;
handleConnectionStatus: (status: boolean) => void;
handleEditorReady: (value: boolean) => void;
handleReadOnlyEditorReady: (value: boolean) => void;
handleConnectionStatus: Dispatch<SetStateAction<boolean>>;
handleEditorReady: Dispatch<SetStateAction<boolean>>;
page: IPage;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
sidePeekVisible: boolean;
};
export const PageEditorBody: React.FC<Props> = observer((props) => {
const {
editorRef,
handleConnectionStatus,
handleEditorReady,
handleReadOnlyEditorReady,
page,
readOnlyEditorRef,
sidePeekVisible,
} = props;
const { editorRef, handleConnectionStatus, handleEditorReady, page, sidePeekVisible } = props;
// router
const { workspaceSlug, projectId } = useParams();
// store hooks
@ -112,11 +100,11 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
const handleServerConnect = useCallback(() => {
handleConnectionStatus(false);
}, []);
}, [handleConnectionStatus]);
const handleServerError = useCallback(() => {
handleConnectionStatus(true);
}, []);
}, [handleConnectionStatus]);
const serverHandler: TServerHandler = useMemo(
() => ({
@ -169,18 +157,16 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
"w-[5%]": isFullWidth,
})}
>
{!isFullWidth && (
<PageContentBrowser editorRef={(isContentEditable ? editorRef : readOnlyEditorRef)?.current} />
)}
{!isFullWidth && <PageContentBrowser editorRef={editorRef.current} />}
</Row>
<div
className={cn("h-full w-full pt-5 duration-200", {
className={cn("size-full pt-5 duration-200", {
"md:w-[calc(100%-10rem)] xl:w-[calc(100%-28rem)]": !isFullWidth,
"md:w-[90%]": isFullWidth,
})}
>
<div className="h-full w-full flex flex-col gap-y-7 overflow-y-auto overflow-x-hidden">
<div className="relative w-full flex-shrink-0 md:pl-5 px-4">
<div className="size-full flex flex-col gap-y-7 overflow-y-auto overflow-x-hidden">
<div className="relative w-full flex-shrink-0 md:pl-5 px-4 h-[38px]">
<PageEditorTitle
editorRef={editorRef}
title={pageTitle}
@ -188,72 +174,47 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
readOnly={!isContentEditable}
/>
</div>
{isContentEditable ? (
<CollaborativeDocumentEditorWithRef
id={pageId}
fileHandler={getEditorFileHandlers({
maxFileSize,
projectId: projectId?.toString() ?? "",
uploadFile: async (file) => {
const { asset_id } = await fileService.uploadProjectAsset(
workspaceSlug?.toString() ?? "",
projectId?.toString() ?? "",
{
entity_identifier: pageId,
entity_type: EFileAssetType.PAGE_DESCRIPTION,
},
file
);
return asset_id;
},
workspaceId,
workspaceSlug: workspaceSlug?.toString() ?? "",
})}
handleEditorReady={handleEditorReady}
ref={editorRef}
containerClassName="h-full p-0 pb-64"
displayConfig={displayConfig}
editorClassName="pl-10"
mentionHandler={{
highlights: mentionHighlights,
suggestions: mentionSuggestions,
}}
embedHandler={{
issue: issueEmbedProps,
}}
realtimeConfig={realtimeConfig}
serverHandler={serverHandler}
user={userConfig}
disabledExtensions={disabledExtensions}
aiHandler={{
menu: getAIMenu,
}}
/>
) : (
<CollaborativeDocumentReadOnlyEditorWithRef
id={pageId}
ref={readOnlyEditorRef}
disabledExtensions={disabledExtensions}
fileHandler={getReadOnlyEditorFileHandlers({
projectId: projectId?.toString() ?? "",
workspaceSlug: workspaceSlug?.toString() ?? "",
})}
handleEditorReady={handleReadOnlyEditorReady}
containerClassName="p-0 pb-64 border-none"
displayConfig={displayConfig}
editorClassName="pl-10"
mentionHandler={{
highlights: mentionHighlights,
}}
embedHandler={{
issue: {
widgetCallback: issueEmbedProps.widgetCallback,
},
}}
realtimeConfig={realtimeConfig}
user={userConfig}
/>
)}
<CollaborativeDocumentEditorWithRef
editable={isContentEditable}
id={pageId}
fileHandler={getEditorFileHandlers({
maxFileSize,
projectId: projectId?.toString() ?? "",
uploadFile: async (file) => {
const { asset_id } = await fileService.uploadProjectAsset(
workspaceSlug?.toString() ?? "",
projectId?.toString() ?? "",
{
entity_identifier: pageId,
entity_type: EFileAssetType.PAGE_DESCRIPTION,
},
file
);
return asset_id;
},
workspaceId,
workspaceSlug: workspaceSlug?.toString() ?? "",
})}
handleEditorReady={handleEditorReady}
ref={editorRef}
containerClassName="h-full p-0 pb-64"
displayConfig={displayConfig}
editorClassName="pl-10"
mentionHandler={{
highlights: mentionHighlights,
suggestions: mentionSuggestions,
}}
embedHandler={{
issue: issueEmbedProps,
}}
realtimeConfig={realtimeConfig}
serverHandler={serverHandler}
user={userConfig}
disabledExtensions={disabledExtensions}
aiHandler={{
menu: getAIMenu,
}}
/>
</div>
</div>
<div

View file

@ -2,7 +2,7 @@
import { observer } from "mobx-react";
// editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// ui
import { ArchiveIcon, FavoriteStar, setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
// components
@ -19,11 +19,10 @@ type Props = {
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
page: IPage;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
};
export const PageExtraOptions: React.FC<Props> = observer((props) => {
const { editorRef, handleDuplicatePage, page, readOnlyEditorRef } = props;
const { editorRef, handleDuplicatePage, page } = props;
// derived values
const {
archived_at,
@ -85,12 +84,8 @@ export const PageExtraOptions: React.FC<Props> = observer((props) => {
iconClassName="text-custom-text-100"
/>
)}
<PageInfoPopover editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current} />
<PageOptionsDropdown
editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current}
handleDuplicatePage={handleDuplicatePage}
page={page}
/>
<PageInfoPopover editorRef={editorRef?.current} />
<PageOptionsDropdown editorRef={editorRef?.current} handleDuplicatePage={handleDuplicatePage} page={page} />
</div>
);
});

View file

@ -2,12 +2,12 @@ import { useState } from "react";
import { usePopper } from "react-popper";
import { Info } from "lucide-react";
// plane editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// helpers
import { getReadTimeFromWordsCount } from "@/helpers/date-time.helper";
type Props = {
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
editorRef: EditorRefApi | null;
};
export const PageInfoPopover: React.FC<Props> = (props) => {

View file

@ -13,52 +13,34 @@ type Props = {
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
page: IPage;
readOnlyEditorReady: boolean;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
setSidePeekVisible: (sidePeekState: boolean) => void;
sidePeekVisible: boolean;
};
export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
const {
editorReady,
editorRef,
handleDuplicatePage,
page,
readOnlyEditorReady,
readOnlyEditorRef,
setSidePeekVisible,
sidePeekVisible,
} = props;
const { editorReady, editorRef, handleDuplicatePage, page, setSidePeekVisible, sidePeekVisible } = props;
// derived values
const { isContentEditable } = page;
// page filters
const { isFullWidth } = usePageFilters();
if (!editorRef.current && !readOnlyEditorRef.current) return null;
if (!editorRef.current) return null;
return (
<>
<Header variant={EHeaderVariant.SECONDARY}>
<div className="flex-shrink-0 my-auto">
<PageSummaryPopover
editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current}
editorRef={editorRef.current}
isFullWidth={isFullWidth}
sidePeekVisible={sidePeekVisible}
setSidePeekVisible={setSidePeekVisible}
/>
</div>
<PageExtraOptions
editorRef={editorRef}
handleDuplicatePage={handleDuplicatePage}
page={page}
readOnlyEditorRef={readOnlyEditorRef}
/>
<PageExtraOptions editorRef={editorRef} handleDuplicatePage={handleDuplicatePage} page={page} />
</Header>
<Header variant={EHeaderVariant.TERNARY}>
{(editorReady || readOnlyEditorReady) && isContentEditable && editorRef.current && (
<PageToolbar editorRef={editorRef?.current} />
)}
{editorReady && isContentEditable && editorRef.current && <PageToolbar editorRef={editorRef?.current} />}
</Header>
</>
);

View file

@ -15,7 +15,7 @@ import {
LucideIcon,
} from "lucide-react";
// document editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// ui
import { ArchiveIcon, CustomMenu, type ISvgIcons, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
// components
@ -30,7 +30,7 @@ import { useQueryParams } from "@/hooks/use-query-params";
import { IPage } from "@/store/pages/page";
type Props = {
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
editorRef: EditorRefApi | null;
handleDuplicatePage: () => void;
page: IPage;
};

View file

@ -1,5 +1,5 @@
import { observer } from "mobx-react";
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// components
import { Header, EHeaderVariant } from "@plane/ui";
import { PageEditorMobileHeaderRoot, PageExtraOptions, PageSummaryPopover, PageToolbar } from "@/components/pages";
@ -15,35 +15,24 @@ type Props = {
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
page: IPage;
readOnlyEditorReady: boolean;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
setSidePeekVisible: (sidePeekState: boolean) => void;
sidePeekVisible: boolean;
};
export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
const {
editorReady,
editorRef,
handleDuplicatePage,
page,
readOnlyEditorReady,
readOnlyEditorRef,
setSidePeekVisible,
sidePeekVisible,
} = props;
const { editorReady, editorRef, setSidePeekVisible, sidePeekVisible, handleDuplicatePage, page } = props;
// derived values
const { isContentEditable } = page;
// page filters
const { isFullWidth } = usePageFilters();
if (!editorRef.current && !readOnlyEditorRef.current) return null;
if (!editorRef.current) return null;
return (
<>
<Header variant={EHeaderVariant.SECONDARY} showOnMobile={false}>
<Header.LeftItem className="gap-0 w-full">
{(editorReady || readOnlyEditorReady) && (
{editorReady && (
<div
className={cn("flex-shrink-0 my-auto", {
"w-40 lg:w-56": !isFullWidth,
@ -51,30 +40,21 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
})}
>
<PageSummaryPopover
editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current}
editorRef={editorRef.current}
isFullWidth={isFullWidth}
sidePeekVisible={sidePeekVisible}
setSidePeekVisible={setSidePeekVisible}
/>
</div>
)}
{(editorReady || readOnlyEditorReady) && isContentEditable && editorRef.current && (
<PageToolbar editorRef={editorRef?.current} />
)}
{editorReady && isContentEditable && editorRef.current && <PageToolbar editorRef={editorRef?.current} />}
</Header.LeftItem>
<PageExtraOptions
editorRef={editorRef}
handleDuplicatePage={handleDuplicatePage}
page={page}
readOnlyEditorRef={readOnlyEditorRef}
/>
<PageExtraOptions editorRef={editorRef} handleDuplicatePage={handleDuplicatePage} page={page} />
</Header>
<div className="md:hidden">
<PageEditorMobileHeaderRoot
editorRef={editorRef}
readOnlyEditorRef={readOnlyEditorRef}
editorReady={editorReady}
readOnlyEditorReady={readOnlyEditorReady}
handleDuplicatePage={handleDuplicatePage}
page={page}
sidePeekVisible={sidePeekVisible}

View file

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
// editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// types
import { TPage } from "@plane/types";
// ui
@ -32,12 +32,10 @@ export const PageRoot = observer((props: TPageRootProps) => {
// states
const [editorReady, setEditorReady] = useState(false);
const [hasConnectionFailed, setHasConnectionFailed] = useState(false);
const [readOnlyEditorReady, setReadOnlyEditorReady] = useState(false);
const [sidePeekVisible, setSidePeekVisible] = useState(window.innerWidth >= 768);
const [isVersionsOverlayOpen, setIsVersionsOverlayOpen] = useState(false);
// refs
const editorRef = useRef<EditorRefApi>(null);
const readOnlyEditorRef = useRef<EditorReadOnlyRefApi>(null);
// router
const router = useAppRouter();
// search params
@ -99,9 +97,7 @@ export const PageRoot = observer((props: TPageRootProps) => {
editorRef.current?.clearEditor();
editorRef.current?.setEditorValue(descriptionHTML);
};
const currentVersionDescription = isContentEditable
? editorRef.current?.getDocument().html
: readOnlyEditorRef.current?.getDocument().html;
const currentVersionDescription = editorRef.current?.getDocument().html;
return (
<>
@ -137,19 +133,15 @@ export const PageRoot = observer((props: TPageRootProps) => {
editorRef={editorRef}
handleDuplicatePage={handleDuplicatePage}
page={page}
readOnlyEditorReady={readOnlyEditorReady}
readOnlyEditorRef={readOnlyEditorRef}
setSidePeekVisible={(state) => setSidePeekVisible(state)}
sidePeekVisible={sidePeekVisible}
/>
<PageEditorBody
editorReady={editorReady}
editorRef={editorRef}
handleConnectionStatus={(status) => setHasConnectionFailed(status)}
handleEditorReady={(val) => setEditorReady(val)}
handleReadOnlyEditorReady={() => setReadOnlyEditorReady(true)}
handleConnectionStatus={setHasConnectionFailed}
handleEditorReady={setEditorReady}
page={page}
readOnlyEditorRef={readOnlyEditorRef}
sidePeekVisible={sidePeekVisible}
/>
</>

View file

@ -1,11 +1,11 @@
import { useState, useEffect } from "react";
// plane editor
import { EditorReadOnlyRefApi, EditorRefApi, IMarking } from "@plane/editor";
import { EditorRefApi, IMarking } from "@plane/editor";
// components
import { OutlineHeading1, OutlineHeading2, OutlineHeading3 } from "./heading-components";
type Props = {
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
editorRef: EditorRefApi | null;
setSidePeekVisible?: (sidePeekState: boolean) => void;
};

View file

@ -2,14 +2,14 @@ import { useState } from "react";
import { usePopper } from "react-popper";
import { List } from "lucide-react";
// document editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// helpers
import { cn } from "@/helpers/common.helper";
// components
import { PageContentBrowser } from "./content-browser";
type Props = {
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
editorRef: EditorRefApi | null;
isFullWidth: boolean;
sidePeekVisible: boolean;
setSidePeekVisible: (sidePeekState: boolean) => void;

View file

@ -4,7 +4,7 @@ import { useState } from "react";
import { PageProps, pdf } from "@react-pdf/renderer";
import { Controller, useForm } from "react-hook-form";
// plane editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// plane ui
import { Button, CustomSelect, EModalPosition, EModalWidth, ModalCore, setToast, TOAST_TYPE } from "@plane/ui";
// components
@ -16,7 +16,7 @@ import {
} from "@/helpers/editor.helper";
type Props = {
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
editorRef: EditorRefApi | null;
isOpen: boolean;
onClose: () => void;
pageTitle: string;

View file

@ -50,6 +50,10 @@ export const useCollaborativePageActions = (editorRef: EditorRefApi | EditorRead
try {
await actionDetails.execute(isPerformedByCurrentUser);
if (isPerformedByCurrentUser) {
const serverEventName = getServerEventName(clientAction);
if (serverEventName) {
editorRef?.emitRealTimeUpdate(serverEventName);
}
setCurrentActionBeingProcessed(clientAction);
}
} catch {
@ -60,18 +64,9 @@ export const useCollaborativePageActions = (editorRef: EditorRefApi | EditorRead
});
}
},
[actionHandlerMap]
[actionHandlerMap, editorRef]
);
useEffect(() => {
if (currentActionBeingProcessed) {
const serverEventName = getServerEventName(currentActionBeingProcessed);
if (serverEventName) {
editorRef?.emitRealTimeUpdate(serverEventName);
}
}
}, [currentActionBeingProcessed, editorRef]);
useEffect(() => {
const realTimeStatelessMessageListener = editorRef?.listenToRealTimeUpdate();
@ -95,6 +90,5 @@ export const useCollaborativePageActions = (editorRef: EditorRefApi | EditorRead
return {
executeCollaborativeAction,
EVENT_ACTION_DETAILS_MAP: actionHandlerMap,
};
};