From d29ab8076293356ae701e379c66fb9c0d416f7bb Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Mon, 9 Dec 2024 20:30:44 +0530 Subject: [PATCH 1/5] fix: removing readonly editor --- .../editors/document/collaborative-editor.tsx | 62 +++++--- .../editors/document/page-renderer.tsx | 13 +- .../components/editors/editor-wrapper.tsx | 2 + .../components/image-uploader.tsx | 24 +-- packages/editor/src/core/extensions/drop.tsx | 97 ++++++------ .../editor/src/core/extensions/extensions.tsx | 9 +- .../core/hooks/use-collaborative-editor.ts | 18 ++- packages/editor/src/core/hooks/use-editor.ts | 68 +++++---- .../editor/src/core/hooks/use-file-upload.ts | 2 +- .../editor/src/core/types/collaboration.ts | 2 + packages/editor/src/core/types/editor.ts | 1 + .../components/icons/syncing-component.tsx | 18 +++ .../components/pages/editor/editor-body.tsx | 142 +++++++----------- .../pages/editor/header/extra-options.tsx | 16 +- .../pages/editor/header/info-popover.tsx | 4 +- .../pages/editor/header/mobile-root.tsx | 28 +--- .../pages/editor/header/options-dropdown.tsx | 4 +- .../components/pages/editor/header/root.tsx | 30 +--- .../components/pages/editor/page-root.tsx | 19 +-- .../pages/editor/summary/content-browser.tsx | 4 +- .../pages/editor/summary/popover.tsx | 4 +- .../pages/modals/export-page-modal.tsx | 4 +- .../hooks/use-collaborative-page-actions.tsx | 16 +- 23 files changed, 296 insertions(+), 291 deletions(-) create mode 100644 web/core/components/icons/syncing-component.tsx diff --git a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx index cd7d6f35489..5a7ff1840a2 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; // components import { DocumentContentLoader, PageRenderer } from "@/components/editors"; // constants @@ -19,6 +19,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { containerClassName, disabledExtensions, displayConfig = DEFAULT_DISPLAY_CONFIG, + editable, editorClassName = "", embedHandler, fileHandler, @@ -43,23 +44,25 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { } // use document editor - const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeEditor({ - onTransaction, - disabledExtensions, - editorClassName, - embedHandler, - extensions, - fileHandler, - forwardedRef, - handleEditorReady, - id, - mentionHandler, - placeholder, - realtimeConfig, - serverHandler, - tabIndex, - user, - }); + const { editor, hasServerConnectionFailed, hasServerSynced, localProvider, hasIndexedDbSynced } = + useCollaborativeEditor({ + disabledExtensions, + editable, + editorClassName, + embedHandler, + extensions, + fileHandler, + forwardedRef, + handleEditorReady, + id, + mentionHandler, + onTransaction, + placeholder, + realtimeConfig, + serverHandler, + tabIndex, + user, + }); const editorContainerClassNames = getEditorClassNames({ noBorder: true, @@ -67,9 +70,30 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { containerClassName, }); + const [hasIndexedDbEntry, setHasIndexedDbEntry] = useState(null); + + useEffect(() => { + async function documentIndexedDbEntry(dbName: string) { + try { + const databases = await indexedDB.databases(); + const hasEntry = databases.some((db) => db.name === dbName); + setHasIndexedDbEntry(hasEntry); + } catch (error) { + console.error("Error checking database existence:", error); + return false; + } + } + documentIndexedDbEntry(id); + }, [id, localProvider]); + if (!editor) return null; - if (!hasServerSynced && !hasServerConnectionFailed) return ; + // Wait until we know about IndexedDB status + if (hasIndexedDbEntry === null) return null; + + if (hasServerConnectionFailed || (!hasIndexedDbEntry && !hasServerSynced) || !hasIndexedDbSynced) { + return ; + } return ( { [editor, cleanup] ); + console.log("rendered"); return ( <>
@@ -139,12 +140,12 @@ export const PageRenderer = (props: IPageRenderer) => { id={id} > - {editor.isEditable && ( - <> - - - - )} + {/* {editor.isEditable && ( */} + {/* <> */} + {/* */} + {/* */} + {/* */} + {/* )} */}
{isOpen && linkViewProps && coordinates && ( diff --git a/packages/editor/src/core/components/editors/editor-wrapper.tsx b/packages/editor/src/core/components/editors/editor-wrapper.tsx index 075420ed74f..7090569625f 100644 --- a/packages/editor/src/core/components/editors/editor-wrapper.tsx +++ b/packages/editor/src/core/components/editors/editor-wrapper.tsx @@ -21,6 +21,7 @@ export const EditorWrapper: React.FC = (props) => { containerClassName, disabledExtensions, displayConfig = DEFAULT_DISPLAY_CONFIG, + editable, editorClassName = "", extensions, id, @@ -38,6 +39,7 @@ export const EditorWrapper: React.FC = (props) => { } = props; const editor = useEditor({ + editable, disabledExtensions, editorClassName, enableHistory: true, diff --git a/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx b/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx index 8ad99bc4439..ef6c28fa65c 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx @@ -127,24 +127,30 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { return "Uploading..."; } - if (draggedInside) { + if (draggedInside && editor.isEditable) { return "Drop image here"; } - return "Add an image"; + if (!editor.isEditable) { + return "Viewing Mode: Image Upload Disabled"; + } else { + return "Add an image"; + } }, [draggedInside, failedToLoadImage, isImageBeingUploaded]); return (
- Extension.create({ - name: "dropHandler", - priority: 1000, +export const DropHandlerExtension = Extension.create({ + name: "dropHandler", + priority: 1000, - addProseMirrorPlugins() { - const editor = this.editor; - return [ - new Plugin({ - key: new PluginKey("drop-handler-plugin"), - props: { - handlePaste: (view: EditorView, event: ClipboardEvent) => { - if (event.clipboardData && event.clipboardData.files && event.clipboardData.files.length > 0) { - event.preventDefault(); - const files = Array.from(event.clipboardData.files); - const imageFiles = files.filter((file) => file.type.startsWith("image")); + addProseMirrorPlugins() { + const editor = this.editor; + return [ + new Plugin({ + key: new PluginKey("drop-handler-plugin"), + props: { + handlePaste: (view: EditorView, event: ClipboardEvent) => { + if ( + editor.isEditable && + event.clipboardData && + event.clipboardData.files && + event.clipboardData.files.length > 0 + ) { + event.preventDefault(); + const files = Array.from(event.clipboardData.files); + const imageFiles = files.filter((file) => file.type.startsWith("image")); - if (imageFiles.length > 0) { - const pos = view.state.selection.from; - insertImagesSafely({ editor, files: imageFiles, initialPos: pos, event: "drop" }); - } - return true; + if (imageFiles.length > 0) { + const pos = view.state.selection.from; + insertImagesSafely({ editor, files: imageFiles, initialPos: pos, event: "drop" }); } - return false; - }, - handleDrop: (view: EditorView, event: DragEvent, _slice: any, moved: boolean) => { - if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length > 0) { - event.preventDefault(); - const files = Array.from(event.dataTransfer.files); - const imageFiles = files.filter((file) => file.type.startsWith("image")); + return true; + } + return false; + }, + handleDrop: (view: EditorView, event: DragEvent, _slice: any, moved: boolean) => { + if ( + editor.isEditable && + !moved && + event.dataTransfer && + event.dataTransfer.files && + event.dataTransfer.files.length > 0 + ) { + event.preventDefault(); + const files = Array.from(event.dataTransfer.files); + const imageFiles = files.filter((file) => file.type.startsWith("image")); - if (imageFiles.length > 0) { - const coordinates = view.posAtCoords({ - left: event.clientX, - top: event.clientY, - }); + if (imageFiles.length > 0) { + const coordinates = view.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); - if (coordinates) { - const pos = coordinates.pos; - insertImagesSafely({ editor, files: imageFiles, initialPos: pos, event: "drop" }); - } - return true; + if (coordinates) { + const pos = coordinates.pos; + insertImagesSafely({ editor, files: imageFiles, initialPos: pos, event: "drop" }); } + return true; } - return false; - }, + } + return false; }, - }), - ]; - }, - }); - + }, + }), + ]; + }, +}); export const insertImagesSafely = async ({ editor, files, diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 0e06f774bb7..4f166ea2a8e 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -47,10 +47,11 @@ type TArguments = { }; placeholder?: string | ((isFocused: boolean, value: string) => string); tabIndex?: number; + editable?: boolean; }; export const CoreEditorExtensions = (args: TArguments): Extensions => { - const { disabledExtensions, enableHistory, fileHandler, mentionConfig, placeholder, tabIndex } = args; + const { disabledExtensions, enableHistory, fileHandler, mentionConfig, placeholder, tabIndex, editable } = args; return [ StarterKit.configure({ @@ -89,7 +90,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { ...(enableHistory ? {} : { history: false }), }), CustomQuoteExtension, - DropHandlerExtension(), + DropHandlerExtension, CustomHorizontalRule.configure({ HTMLAttributes: { class: "py-4 border-custom-border-400", @@ -145,9 +146,9 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { TableCell, TableRow, CustomMention({ - mentionSuggestions: mentionConfig.mentionSuggestions, + mentionSuggestions: editable ? mentionConfig.mentionSuggestions : undefined, mentionHighlights: mentionConfig.mentionHighlights, - readonly: false, + readonly: !editable, }), Placeholder.configure({ placeholder: ({ editor, node }) => { diff --git a/packages/editor/src/core/hooks/use-collaborative-editor.ts b/packages/editor/src/core/hooks/use-collaborative-editor.ts index b3c7d6cfc2e..c6506dd127e 100644 --- a/packages/editor/src/core/hooks/use-collaborative-editor.ts +++ b/packages/editor/src/core/hooks/use-collaborative-editor.ts @@ -15,6 +15,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { const { onTransaction, disabledExtensions, + editable, editorClassName, editorProps = {}, embedHandler, @@ -33,6 +34,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { // states const [hasServerConnectionFailed, setHasServerConnectionFailed] = useState(false); const [hasServerSynced, setHasServerSynced] = useState(false); + const [hasIndexedDbSynced, setHasIndexedDbSynced] = useState(false); // initialize Hocuspocus provider const provider = useMemo( () => @@ -53,7 +55,10 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { setHasServerConnectionFailed(true); } }, - onSynced: () => setHasServerSynced(true), + onSynced: () => { + serverHandler?.onServerSync?.(); + setHasServerSynced(true); + }, }), [id, realtimeConfig, serverHandler, user] ); @@ -63,6 +68,10 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { [id, provider] ); + localProvider?.on("synced", () => { + setHasIndexedDbSynced(true); + }); + // destroy and disconnect all providers connection on unmount useEffect( () => () => { @@ -75,7 +84,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { const editor = useEditor({ disabledExtensions, id, - onTransaction, + editable, editorProps, editorClassName, enableHistory: false, @@ -97,9 +106,10 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { }), ], fileHandler, - handleEditorReady, forwardedRef, + handleEditorReady, mentionHandler, + onTransaction, placeholder, provider, tabIndex, @@ -109,5 +119,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { editor, hasServerConnectionFailed, hasServerSynced, + hasIndexedDbSynced, + localProvider, }; }; diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index 15fbd19d5c8..05e48e93acf 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -27,6 +27,7 @@ import type { } from "@/types"; export interface CustomEditorProps { + editable: boolean; editorClassName: string; editorProps?: EditorProps; enableHistory: boolean; @@ -55,6 +56,7 @@ export interface CustomEditorProps { export const useEditor = (props: CustomEditorProps) => { const { disabledExtensions, + editable, editorClassName, editorProps = {}, enableHistory, @@ -74,42 +76,46 @@ export const useEditor = (props: CustomEditorProps) => { autofocus = false, } = props; // states - const [savedSelection, setSavedSelection] = useState(null); // refs const editorRef: MutableRefObject = useRef(null); const savedSelectionRef = useRef(savedSelection); - const editor = useTiptapEditor({ - autofocus, - editorProps: { - ...CoreEditorProps({ - editorClassName, - }), - ...editorProps, - }, - extensions: [ - ...CoreEditorExtensions({ - disabledExtensions, - enableHistory, - fileHandler, - mentionConfig: { - mentionSuggestions: mentionHandler.suggestions ?? (() => Promise.resolve([])), - mentionHighlights: mentionHandler.highlights, - }, - placeholder, - tabIndex, - }), - ...extensions, - ], - content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "

", - onCreate: () => handleEditorReady?.(true), - onTransaction: ({ editor }) => { - setSavedSelection(editor.state.selection); - onTransaction?.(); + const editor = useTiptapEditor( + { + editable, + autofocus, + editorProps: { + ...CoreEditorProps({ + editorClassName, + }), + ...editorProps, + }, + extensions: [ + ...CoreEditorExtensions({ + editable, + disabledExtensions, + enableHistory, + fileHandler, + mentionConfig: { + mentionSuggestions: mentionHandler.suggestions ?? (() => Promise.resolve([])), + mentionHighlights: mentionHandler.highlights, + }, + placeholder, + tabIndex, + }), + ...extensions, + ], + content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "

", + onCreate: () => handleEditorReady?.(true), + onTransaction: ({ editor }) => { + setSavedSelection(editor.state.selection); + onTransaction?.(); + }, + onUpdate: ({ editor }) => onChange?.(editor.getJSON(), editor.getHTML()), + onDestroy: () => handleEditorReady?.(false), }, - onUpdate: ({ editor }) => onChange?.(editor.getJSON(), editor.getHTML()), - onDestroy: () => handleEditorReady?.(false), - }); + [editable] + ); // Update the ref whenever savedSelection changes useEffect(() => { diff --git a/packages/editor/src/core/hooks/use-file-upload.ts b/packages/editor/src/core/hooks/use-file-upload.ts index f5f930f2900..65daa2f8e49 100644 --- a/packages/editor/src/core/hooks/use-file-upload.ts +++ b/packages/editor/src/core/hooks/use-file-upload.ts @@ -105,7 +105,7 @@ export const useDropZone = (args: TDropzoneArgs) => { async (e: DragEvent) => { e.preventDefault(); setDraggedInside(false); - if (e.dataTransfer.files.length === 0) { + if (e.dataTransfer.files.length === 0 || !editor.isEditable) { return; } const filesList = e.dataTransfer.files; diff --git a/packages/editor/src/core/types/collaboration.ts b/packages/editor/src/core/types/collaboration.ts index 35fbdb99680..0c0079b00b3 100644 --- a/packages/editor/src/core/types/collaboration.ts +++ b/packages/editor/src/core/types/collaboration.ts @@ -17,10 +17,12 @@ import { export type TServerHandler = { onConnect?: () => void; onServerError?: () => void; + onServerSync?: () => void; }; type TCollaborativeEditorHookProps = { disabledExtensions: TExtensions[]; + editable?: boolean; editorClassName: string; editorProps?: EditorProps; extensions?: Extensions; diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index e91af8e4923..25d22e439d1 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -106,6 +106,7 @@ export interface EditorRefApi extends EditorReadOnlyRefApi { // editor props export interface IEditorProps { + editable: boolean; containerClassName?: string; displayConfig?: TDisplayConfig; disabledExtensions: TExtensions[]; diff --git a/web/core/components/icons/syncing-component.tsx b/web/core/components/icons/syncing-component.tsx new file mode 100644 index 00000000000..1397c9cf23c --- /dev/null +++ b/web/core/components/icons/syncing-component.tsx @@ -0,0 +1,18 @@ +import { RefreshCcw } from "lucide-react"; +import { Tooltip } from "@plane/ui"; + +export const SyncingComponent = (props: { toolTipContent?: string }) => { + const { toolTipContent } = props; + const lockedComponent = ( +
+ + Syncing +
+ ); + + return ( + <> + {toolTipContent ? {lockedComponent} : <>{lockedComponent}} + + ); +}; diff --git a/web/core/components/pages/editor/editor-body.tsx b/web/core/components/pages/editor/editor-body.tsx index 6f88445ede9..7b729477075 100644 --- a/web/core/components/pages/editor/editor-body.tsx +++ b/web/core/components/pages/editor/editor-body.tsx @@ -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,15 @@ const fileService = new FileService(); type Props = { editorRef: React.RefObject; editorReady: boolean; - handleConnectionStatus: (status: boolean) => void; - handleEditorReady: (value: boolean) => void; - handleReadOnlyEditorReady: (value: boolean) => void; + handleConnectionStatus: Dispatch>; + handleEditorReady: Dispatch>; page: IPage; - readOnlyEditorRef: React.RefObject; sidePeekVisible: boolean; + setSyncing: (value: boolean) => void; }; export const PageEditorBody: React.FC = observer((props) => { - const { - editorRef, - handleConnectionStatus, - handleEditorReady, - handleReadOnlyEditorReady, - page, - readOnlyEditorRef, - sidePeekVisible, - } = props; + const { editorRef, handleConnectionStatus, handleEditorReady, page, sidePeekVisible, setSyncing } = props; // router const { workspaceSlug, projectId } = useParams(); // store hooks @@ -118,12 +107,17 @@ export const PageEditorBody: React.FC = observer((props) => { handleConnectionStatus(true); }, []); + const handleServerSynced = useCallback(() => { + setSyncing(true); + }, []); + const serverHandler: TServerHandler = useMemo( () => ({ onConnect: handleServerConnect, onServerError: handleServerError, + onServerSync: handleServerSynced, }), - [handleServerConnect, handleServerError] + [handleServerConnect, handleServerError, handleServerSynced] ); const realtimeConfig: TRealtimeConfig | undefined = useMemo(() => { @@ -169,9 +163,7 @@ export const PageEditorBody: React.FC = observer((props) => { "w-[5%]": isFullWidth, })} > - {!isFullWidth && ( - - )} + {!isFullWidth && }
= observer((props) => { readOnly={!isContentEditable} />
- {isContentEditable ? ( - { - 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, - }} - /> - ) : ( - - )} + { + 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, + }} + /> + )
; handleDuplicatePage: () => void; page: IPage; - readOnlyEditorRef: React.RefObject; + syncState: boolean | null; }; export const PageExtraOptions: React.FC = observer((props) => { - const { editorRef, handleDuplicatePage, page, readOnlyEditorRef } = props; + const { editorRef, syncState, handleDuplicatePage, page } = props; // derived values const { archived_at, @@ -60,6 +61,7 @@ export const PageExtraOptions: React.FC = observer((props) => { return (
{is_locked && } + {!syncState && } {archived_at && (
@@ -85,12 +87,8 @@ export const PageExtraOptions: React.FC = observer((props) => { iconClassName="text-custom-text-100" /> )} - - + +
); }); diff --git a/web/core/components/pages/editor/header/info-popover.tsx b/web/core/components/pages/editor/header/info-popover.tsx index e295d8ea278..13a1b17666a 100644 --- a/web/core/components/pages/editor/header/info-popover.tsx +++ b/web/core/components/pages/editor/header/info-popover.tsx @@ -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) => { diff --git a/web/core/components/pages/editor/header/mobile-root.tsx b/web/core/components/pages/editor/header/mobile-root.tsx index ac831796cbe..df93a70e922 100644 --- a/web/core/components/pages/editor/header/mobile-root.tsx +++ b/web/core/components/pages/editor/header/mobile-root.tsx @@ -13,52 +13,34 @@ type Props = { editorRef: React.RefObject; handleDuplicatePage: () => void; page: IPage; - readOnlyEditorReady: boolean; - readOnlyEditorRef: React.RefObject; setSidePeekVisible: (sidePeekState: boolean) => void; sidePeekVisible: boolean; }; export const PageEditorMobileHeaderRoot: React.FC = 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 ( <>
- +
- {(editorReady || readOnlyEditorReady) && isContentEditable && editorRef.current && ( - - )} + {editorReady && isContentEditable && editorRef.current && }
); diff --git a/web/core/components/pages/editor/header/options-dropdown.tsx b/web/core/components/pages/editor/header/options-dropdown.tsx index ff0987a9dc2..a1db6f97241 100644 --- a/web/core/components/pages/editor/header/options-dropdown.tsx +++ b/web/core/components/pages/editor/header/options-dropdown.tsx @@ -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; }; diff --git a/web/core/components/pages/editor/header/root.tsx b/web/core/components/pages/editor/header/root.tsx index 9640f4e43b6..d8001faf000 100644 --- a/web/core/components/pages/editor/header/root.tsx +++ b/web/core/components/pages/editor/header/root.tsx @@ -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,25 @@ type Props = { editorRef: React.RefObject; handleDuplicatePage: () => void; page: IPage; - readOnlyEditorReady: boolean; - readOnlyEditorRef: React.RefObject; setSidePeekVisible: (sidePeekState: boolean) => void; sidePeekVisible: boolean; + syncState: boolean | null; }; export const PageEditorHeaderRoot: React.FC = observer((props) => { - const { - editorReady, - editorRef, - handleDuplicatePage, - page, - readOnlyEditorReady, - readOnlyEditorRef, - setSidePeekVisible, - sidePeekVisible, - } = props; + const { editorReady, editorRef, setSidePeekVisible, sidePeekVisible, handleDuplicatePage, page, syncState } = props; // derived values const { isContentEditable } = page; // page filters const { isFullWidth } = usePageFilters(); - if (!editorRef.current && !readOnlyEditorRef.current) return null; + if (!editorRef.current) return null; return ( <>
- {(editorReady || readOnlyEditorReady) && ( + {editorReady && (
= observer((props) => { })} >
)} - {(editorReady || readOnlyEditorReady) && isContentEditable && editorRef.current && ( - - )} + {editorReady && isContentEditable && editorRef.current && }
{ // states const [editorReady, setEditorReady] = useState(false); const [hasConnectionFailed, setHasConnectionFailed] = useState(false); - const [readOnlyEditorReady, setReadOnlyEditorReady] = useState(false); const [sidePeekVisible, setSidePeekVisible] = useState(window.innerWidth >= 768); + const [syncState, setSyncing] = useState(null); const [isVersionsOverlayOpen, setIsVersionsOverlayOpen] = useState(false); // refs const editorRef = useRef(null); - const readOnlyEditorRef = useRef(null); // router const router = useAppRouter(); // search params @@ -99,9 +98,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,20 +134,18 @@ export const PageRoot = observer((props: TPageRootProps) => { editorRef={editorRef} handleDuplicatePage={handleDuplicatePage} page={page} - readOnlyEditorReady={readOnlyEditorReady} - readOnlyEditorRef={readOnlyEditorRef} setSidePeekVisible={(state) => setSidePeekVisible(state)} sidePeekVisible={sidePeekVisible} + syncState={syncState} /> setHasConnectionFailed(status)} - handleEditorReady={(val) => setEditorReady(val)} - handleReadOnlyEditorReady={() => setReadOnlyEditorReady(true)} + handleConnectionStatus={setHasConnectionFailed} + handleEditorReady={setEditorReady} page={page} - readOnlyEditorRef={readOnlyEditorRef} sidePeekVisible={sidePeekVisible} + setSyncing={setSyncing} /> ); diff --git a/web/core/components/pages/editor/summary/content-browser.tsx b/web/core/components/pages/editor/summary/content-browser.tsx index 669d2e978c8..16d818aaeb7 100644 --- a/web/core/components/pages/editor/summary/content-browser.tsx +++ b/web/core/components/pages/editor/summary/content-browser.tsx @@ -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; }; diff --git a/web/core/components/pages/editor/summary/popover.tsx b/web/core/components/pages/editor/summary/popover.tsx index 5d14234f037..9acc4a7cc0c 100644 --- a/web/core/components/pages/editor/summary/popover.tsx +++ b/web/core/components/pages/editor/summary/popover.tsx @@ -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; diff --git a/web/core/components/pages/modals/export-page-modal.tsx b/web/core/components/pages/modals/export-page-modal.tsx index cf4f0a0f4b5..acf4ff08311 100644 --- a/web/core/components/pages/modals/export-page-modal.tsx +++ b/web/core/components/pages/modals/export-page-modal.tsx @@ -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; diff --git a/web/core/hooks/use-collaborative-page-actions.tsx b/web/core/hooks/use-collaborative-page-actions.tsx index 6ec9f799050..6196929b6a4 100644 --- a/web/core/hooks/use-collaborative-page-actions.tsx +++ b/web/core/hooks/use-collaborative-page-actions.tsx @@ -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, }; }; From 2b06dfa3f68a69aea81491bae10ca73516d150f6 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Mon, 16 Dec 2024 19:47:23 +0530 Subject: [PATCH 2/5] fix: sync state --- web/core/components/pages/editor/editor-body.tsx | 8 ++++---- .../components/pages/editor/header/extra-options.tsx | 2 +- .../components/pages/editor/header/mobile-root.tsx | 10 ++++++++-- web/core/components/pages/editor/header/root.tsx | 3 ++- web/core/components/pages/editor/page-root.tsx | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/web/core/components/pages/editor/editor-body.tsx b/web/core/components/pages/editor/editor-body.tsx index 7b729477075..8cf794d9b4a 100644 --- a/web/core/components/pages/editor/editor-body.tsx +++ b/web/core/components/pages/editor/editor-body.tsx @@ -101,15 +101,15 @@ export const PageEditorBody: React.FC = observer((props) => { const handleServerConnect = useCallback(() => { handleConnectionStatus(false); - }, []); + }, [handleConnectionStatus]); const handleServerError = useCallback(() => { handleConnectionStatus(true); - }, []); + }, [handleConnectionStatus]); const handleServerSynced = useCallback(() => { - setSyncing(true); - }, []); + setSyncing(false); + }, [setSyncing]); const serverHandler: TServerHandler = useMemo( () => ({ diff --git a/web/core/components/pages/editor/header/extra-options.tsx b/web/core/components/pages/editor/header/extra-options.tsx index 50dd1eda239..2eb76659fe6 100644 --- a/web/core/components/pages/editor/header/extra-options.tsx +++ b/web/core/components/pages/editor/header/extra-options.tsx @@ -61,7 +61,7 @@ export const PageExtraOptions: React.FC = observer((props) => { return (
{is_locked && } - {!syncState && } + {syncState && } {archived_at && (
diff --git a/web/core/components/pages/editor/header/mobile-root.tsx b/web/core/components/pages/editor/header/mobile-root.tsx index df93a70e922..0987d17d6fe 100644 --- a/web/core/components/pages/editor/header/mobile-root.tsx +++ b/web/core/components/pages/editor/header/mobile-root.tsx @@ -15,10 +15,11 @@ type Props = { page: IPage; setSidePeekVisible: (sidePeekState: boolean) => void; sidePeekVisible: boolean; + syncState: boolean; }; export const PageEditorMobileHeaderRoot: React.FC = observer((props) => { - const { editorReady, editorRef, handleDuplicatePage, page, setSidePeekVisible, sidePeekVisible } = props; + const { editorReady, editorRef, handleDuplicatePage, page, setSidePeekVisible, sidePeekVisible, syncState } = props; // derived values const { isContentEditable } = page; // page filters @@ -37,7 +38,12 @@ export const PageEditorMobileHeaderRoot: React.FC = observer((props) => { setSidePeekVisible={setSidePeekVisible} />
- +
{editorReady && isContentEditable && editorRef.current && } diff --git a/web/core/components/pages/editor/header/root.tsx b/web/core/components/pages/editor/header/root.tsx index d8001faf000..8c88b83f6ee 100644 --- a/web/core/components/pages/editor/header/root.tsx +++ b/web/core/components/pages/editor/header/root.tsx @@ -17,7 +17,7 @@ type Props = { page: IPage; setSidePeekVisible: (sidePeekState: boolean) => void; sidePeekVisible: boolean; - syncState: boolean | null; + syncState: boolean; }; export const PageEditorHeaderRoot: React.FC = observer((props) => { @@ -65,6 +65,7 @@ export const PageEditorHeaderRoot: React.FC = observer((props) => { page={page} sidePeekVisible={sidePeekVisible} setSidePeekVisible={setSidePeekVisible} + syncState={syncState} />
diff --git a/web/core/components/pages/editor/page-root.tsx b/web/core/components/pages/editor/page-root.tsx index 6a300d68891..19c0779e9ff 100644 --- a/web/core/components/pages/editor/page-root.tsx +++ b/web/core/components/pages/editor/page-root.tsx @@ -33,7 +33,7 @@ export const PageRoot = observer((props: TPageRootProps) => { const [editorReady, setEditorReady] = useState(false); const [hasConnectionFailed, setHasConnectionFailed] = useState(false); const [sidePeekVisible, setSidePeekVisible] = useState(window.innerWidth >= 768); - const [syncState, setSyncing] = useState(null); + const [syncState, setSyncing] = useState(true); const [isVersionsOverlayOpen, setIsVersionsOverlayOpen] = useState(false); // refs const editorRef = useRef(null); From 09f2be4d63d9a6f4c7c4e0527552b2d7d7253fed Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Mon, 16 Dec 2024 20:02:59 +0530 Subject: [PATCH 3/5] fix: indexeddb sync loader added --- .../editors/document/collaborative-editor.tsx | 11 +++++++---- .../components/editors/document/page-renderer.tsx | 1 - web/core/components/pages/editor/editor-body.tsx | 1 - 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx index 5a7ff1840a2..5accb9133dc 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -88,10 +88,13 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { if (!editor) return null; - // Wait until we know about IndexedDB status - if (hasIndexedDbEntry === null) return null; - - if (hasServerConnectionFailed || (!hasIndexedDbEntry && !hasServerSynced) || !hasIndexedDbSynced) { + if ( + hasServerConnectionFailed || + (!hasIndexedDbEntry && !hasServerSynced) || + !hasIndexedDbSynced || + !hasIndexedDbEntry + ) { + console.log("syncing indexedDB"); return ; } diff --git a/packages/editor/src/core/components/editors/document/page-renderer.tsx b/packages/editor/src/core/components/editors/document/page-renderer.tsx index 7e190a640ee..15fe8e7bbc6 100644 --- a/packages/editor/src/core/components/editors/document/page-renderer.tsx +++ b/packages/editor/src/core/components/editors/document/page-renderer.tsx @@ -129,7 +129,6 @@ export const PageRenderer = (props: IPageRenderer) => { [editor, cleanup] ); - console.log("rendered"); return ( <>
diff --git a/web/core/components/pages/editor/editor-body.tsx b/web/core/components/pages/editor/editor-body.tsx index 8cf794d9b4a..41066a57c30 100644 --- a/web/core/components/pages/editor/editor-body.tsx +++ b/web/core/components/pages/editor/editor-body.tsx @@ -221,7 +221,6 @@ export const PageEditorBody: React.FC = observer((props) => { menu: getAIMenu, }} /> - )
Date: Mon, 16 Dec 2024 21:02:52 +0530 Subject: [PATCH 4/5] fix: remove node error fixed --- .../components/editors/document/page-renderer.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/editor/src/core/components/editors/document/page-renderer.tsx b/packages/editor/src/core/components/editors/document/page-renderer.tsx index 15fe8e7bbc6..f291c8b3a3f 100644 --- a/packages/editor/src/core/components/editors/document/page-renderer.tsx +++ b/packages/editor/src/core/components/editors/document/page-renderer.tsx @@ -139,12 +139,12 @@ export const PageRenderer = (props: IPageRenderer) => { id={id} > - {/* {editor.isEditable && ( */} - {/* <> */} - {/* */} - {/* */} - {/* */} - {/* )} */} + {editor.isEditable && ( +
+ + +
+ )}
{isOpen && linkViewProps && coordinates && ( From a7ddf3dc155902bb55f2ac84382ec97507798773 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 17 Dec 2024 13:46:21 +0530 Subject: [PATCH 5/5] style: page title and checkbox --- .../collaborative-read-only-editor.tsx | 81 ---------------- .../core/components/editors/document/index.ts | 1 - .../editor/src/core/extensions/extensions.tsx | 3 + .../core/extensions/read-only-extensions.tsx | 2 - .../use-read-only-collaborative-editor.ts | 92 ------------------- packages/editor/src/index.ts | 1 - packages/editor/src/styles/editor.css | 12 +-- .../components/pages/editor/editor-body.tsx | 6 +- 8 files changed, 12 insertions(+), 186 deletions(-) delete mode 100644 packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx delete mode 100644 packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts diff --git a/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx deleted file mode 100644 index 89acace7b70..00000000000 --- a/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { forwardRef, MutableRefObject } from "react"; -// components -import { DocumentContentLoader, PageRenderer } from "@/components/editors"; -// constants -import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config"; -// extensions -import { IssueWidget } from "@/extensions"; -// helpers -import { getEditorClassNames } from "@/helpers/common"; -// hooks -import { useReadOnlyCollaborativeEditor } from "@/hooks/use-read-only-collaborative-editor"; -// types -import { EditorReadOnlyRefApi, ICollaborativeDocumentReadOnlyEditor } from "@/types"; - -const CollaborativeDocumentReadOnlyEditor = (props: ICollaborativeDocumentReadOnlyEditor) => { - const { - containerClassName, - disabledExtensions, - displayConfig = DEFAULT_DISPLAY_CONFIG, - editorClassName = "", - embedHandler, - fileHandler, - forwardedRef, - handleEditorReady, - id, - mentionHandler, - realtimeConfig, - serverHandler, - user, - } = props; - const extensions = []; - if (embedHandler?.issue) { - extensions.push( - IssueWidget({ - widgetCallback: embedHandler.issue.widgetCallback, - }) - ); - } - - const { editor, hasServerConnectionFailed, hasServerSynced } = useReadOnlyCollaborativeEditor({ - disabledExtensions, - editorClassName, - extensions, - fileHandler, - forwardedRef, - handleEditorReady, - id, - mentionHandler, - realtimeConfig, - serverHandler, - user, - }); - - const editorContainerClassName = getEditorClassNames({ - containerClassName, - }); - - if (!editor) return null; - - if (!hasServerSynced && !hasServerConnectionFailed) return ; - - return ( - - ); -}; - -const CollaborativeDocumentReadOnlyEditorWithRef = forwardRef< - EditorReadOnlyRefApi, - ICollaborativeDocumentReadOnlyEditor ->((props, ref) => ( - } /> -)); - -CollaborativeDocumentReadOnlyEditorWithRef.displayName = "CollaborativeDocumentReadOnlyEditorWithRef"; - -export { CollaborativeDocumentReadOnlyEditorWithRef }; diff --git a/packages/editor/src/core/components/editors/document/index.ts b/packages/editor/src/core/components/editors/document/index.ts index 514b620e3a2..571cb7e9a1f 100644 --- a/packages/editor/src/core/components/editors/document/index.ts +++ b/packages/editor/src/core/components/editors/document/index.ts @@ -1,5 +1,4 @@ export * from "./collaborative-editor"; -export * from "./collaborative-read-only-editor"; export * from "./loader"; export * from "./page-renderer"; export * from "./read-only-editor"; diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 4f166ea2a8e..706c56143d1 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -138,6 +138,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { CustomCodeInlineExtension, Markdown.configure({ html: true, + transformCopiedText: true, transformPastedText: true, breaks: true, }), @@ -152,6 +153,8 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { }), Placeholder.configure({ placeholder: ({ editor, node }) => { + if (!editor.isEditable) return; + if (node.type.name === "heading") return `Heading ${node.attrs.level}`; if (editor.storage.imageComponent.uploadInProgress) return ""; diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.tsx index 4debda019c1..7e9b055210b 100644 --- a/packages/editor/src/core/extensions/read-only-extensions.tsx +++ b/packages/editor/src/core/extensions/read-only-extensions.tsx @@ -20,7 +20,6 @@ import { TableRow, Table, CustomMention, - HeadingListExtension, CustomReadOnlyImageExtension, CustomTextAlignExtension, CustomCalloutReadOnlyExtension, @@ -139,7 +138,6 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { }), CharacterCount, CustomColorExtension, - HeadingListExtension, CustomTextAlignExtension, CustomCalloutReadOnlyExtension, ...CoreReadOnlyEditorAdditionalExtensions({ diff --git a/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts b/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts deleted file mode 100644 index 01ca19b8148..00000000000 --- a/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { HocuspocusProvider } from "@hocuspocus/provider"; -import Collaboration from "@tiptap/extension-collaboration"; -import { IndexeddbPersistence } from "y-indexeddb"; -// extensions -import { HeadingListExtension } from "@/extensions"; -// hooks -import { useReadOnlyEditor } from "@/hooks/use-read-only-editor"; -// types -import { TReadOnlyCollaborativeEditorProps } from "@/types"; - -export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEditorProps) => { - const { - disabledExtensions, - editorClassName, - editorProps = {}, - extensions, - fileHandler, - forwardedRef, - handleEditorReady, - id, - mentionHandler, - realtimeConfig, - serverHandler, - user, - } = props; - // states - const [hasServerConnectionFailed, setHasServerConnectionFailed] = useState(false); - const [hasServerSynced, setHasServerSynced] = useState(false); - // initialize Hocuspocus provider - const provider = useMemo( - () => - new HocuspocusProvider({ - name: id, - url: realtimeConfig.url, - token: JSON.stringify(user), - parameters: realtimeConfig.queryParams, - onAuthenticationFailed: () => { - serverHandler?.onServerError?.(); - setHasServerConnectionFailed(true); - }, - onConnect: () => serverHandler?.onConnect?.(), - onClose: (data) => { - if (data.event.code === 1006) { - serverHandler?.onServerError?.(); - setHasServerConnectionFailed(true); - } - }, - onSynced: () => setHasServerSynced(true), - }), - [id, realtimeConfig, serverHandler, user] - ); - - // indexed db integration for offline support - const localProvider = useMemo( - () => (id ? new IndexeddbPersistence(id, provider.document) : undefined), - [id, provider] - ); - - // destroy and disconnect connection on unmount - useEffect( - () => () => { - provider.destroy(); - localProvider?.destroy(); - }, - [provider, localProvider] - ); - - const editor = useReadOnlyEditor({ - disabledExtensions, - editorProps, - editorClassName, - extensions: [ - ...(extensions ?? []), - HeadingListExtension, - Collaboration.configure({ - document: provider.document, - }), - ], - fileHandler, - forwardedRef, - handleEditorReady, - mentionHandler, - provider, - }); - - return { - editor, - hasServerConnectionFailed, - hasServerSynced, - }; -}; diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index ed7d9134698..9dd0db267f2 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -9,7 +9,6 @@ import "./styles/drag-drop.css"; // editors export { CollaborativeDocumentEditorWithRef, - CollaborativeDocumentReadOnlyEditorWithRef, DocumentReadOnlyEditorWithRef, LiteTextEditorWithRef, LiteTextReadOnlyEditorWithRef, diff --git a/packages/editor/src/styles/editor.css b/packages/editor/src/styles/editor.css index db60c7cf5f5..e234f87cf86 100644 --- a/packages/editor/src/styles/editor.css +++ b/packages/editor/src/styles/editor.css @@ -111,8 +111,12 @@ ul[data-type="taskList"] li > label input[type="checkbox"] { transform: scale(1.05); } -ul[data-type="taskList"] li > label input[type="checkbox"]:hover { - background-color: rgba(var(--color-background-80)) !important; +.ProseMirror[contenteditable="true"] input[type="checkbox"]:hover { + background-color: rgba(var(--color-background-80)); +} + +.ProseMirror[contenteditable="false"] input[type="checkbox"] { + pointer-events: none; } ul[data-type="taskList"] li > label input[type="checkbox"][checked] { @@ -151,10 +155,6 @@ ul[data-type="taskList"] li > label input[type="checkbox"] { margin-right: 0.2rem; margin-top: 0.15rem; - &:hover { - background-color: rgb(var(--color-background-80)); - } - &:active { background-color: rgb(var(--color-background-90)); } diff --git a/web/core/components/pages/editor/editor-body.tsx b/web/core/components/pages/editor/editor-body.tsx index 41066a57c30..876a3db7f27 100644 --- a/web/core/components/pages/editor/editor-body.tsx +++ b/web/core/components/pages/editor/editor-body.tsx @@ -166,13 +166,13 @@ export const PageEditorBody: React.FC = observer((props) => { {!isFullWidth && }
-
-
+
+