From 90e63fb625a5f70f0d828c99732c2fff59c945fc Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 18 Jul 2025 16:36:46 +0530 Subject: [PATCH 1/5] refactor: add isTouchDevice prop --- .../src/ce/extensions/core/extensions.ts | 2 +- .../extensions/core/read-only-extensions.ts | 2 +- .../src/ce/extensions/document-extensions.tsx | 2 +- .../ce/extensions/rich-text/extensions.tsx | 2 +- .../rich-text/read-only-extensions.tsx | 31 -------------- .../editors/document/collaborative-editor.tsx | 3 ++ .../editors/document/page-renderer.tsx | 17 ++++++-- .../editors/document/read-only-editor.tsx | 2 + .../components/editors/editor-container.tsx | 14 ++++--- .../components/editors/editor-wrapper.tsx | 2 + .../editors/read-only-editor-wrapper.tsx | 2 + .../components/editors/rich-text/editor.tsx | 2 + .../custom-image/components/block.tsx | 15 +++++-- .../components/toolbar/full-screen/modal.tsx | 41 +++++++++++-------- .../components/toolbar/full-screen/root.tsx | 15 ++++--- .../custom-image/components/toolbar/root.tsx | 21 ++++++---- .../custom-image/components/uploader.tsx | 7 +++- .../core/extensions/custom-image/extension.ts | 1 + .../src/core/extensions/custom-image/types.ts | 1 + .../editor/src/core/extensions/extensions.ts | 14 ++++++- .../extensions/image/extension-config.tsx | 2 +- .../src/core/extensions/image/extension.tsx | 1 + .../core/extensions/read-only-extensions.ts | 12 +++++- .../editor/src/core/helpers/scroll-to-node.ts | 1 + .../core/hooks/use-collaborative-editor.ts | 3 ++ packages/editor/src/core/hooks/use-editor.ts | 2 + packages/editor/src/core/types/editor.ts | 3 ++ packages/editor/src/core/types/hook.ts | 2 +- 28 files changed, 137 insertions(+), 85 deletions(-) delete mode 100644 packages/editor/src/ce/extensions/rich-text/read-only-extensions.tsx diff --git a/packages/editor/src/ce/extensions/core/extensions.ts b/packages/editor/src/ce/extensions/core/extensions.ts index a72bcc2159d..babf7312081 100644 --- a/packages/editor/src/ce/extensions/core/extensions.ts +++ b/packages/editor/src/ce/extensions/core/extensions.ts @@ -4,7 +4,7 @@ import type { IEditorProps } from "@/types"; export type TCoreAdditionalExtensionsProps = Pick< IEditorProps, - "disabledExtensions" | "flaggedExtensions" | "fileHandler" + "disabledExtensions" | "flaggedExtensions" | "fileHandler" | "isTouchDevice" >; export const CoreEditorAdditionalExtensions = (props: TCoreAdditionalExtensionsProps): Extensions => { diff --git a/packages/editor/src/ce/extensions/core/read-only-extensions.ts b/packages/editor/src/ce/extensions/core/read-only-extensions.ts index 4f9306da302..1937962eb08 100644 --- a/packages/editor/src/ce/extensions/core/read-only-extensions.ts +++ b/packages/editor/src/ce/extensions/core/read-only-extensions.ts @@ -4,7 +4,7 @@ import type { IReadOnlyEditorProps } from "@/types"; export type TCoreReadOnlyEditorAdditionalExtensionsProps = Pick< IReadOnlyEditorProps, - "disabledExtensions" | "flaggedExtensions" + "disabledExtensions" | "flaggedExtensions" | "isTouchDevice" >; export const CoreReadOnlyEditorAdditionalExtensions = ( diff --git a/packages/editor/src/ce/extensions/document-extensions.tsx b/packages/editor/src/ce/extensions/document-extensions.tsx index 8815e2d26e9..2a1331fa773 100644 --- a/packages/editor/src/ce/extensions/document-extensions.tsx +++ b/packages/editor/src/ce/extensions/document-extensions.tsx @@ -8,7 +8,7 @@ import type { IEditorProps, TExtensions, TUserDetails } from "@/types"; export type TDocumentEditorAdditionalExtensionsProps = Pick< IEditorProps, - "disabledExtensions" | "flaggedExtensions" | "fileHandler" + "disabledExtensions" | "flaggedExtensions" | "fileHandler" | "isTouchDevice" > & { embedConfig: TEmbedConfig | undefined; provider?: HocuspocusProvider; diff --git a/packages/editor/src/ce/extensions/rich-text/extensions.tsx b/packages/editor/src/ce/extensions/rich-text/extensions.tsx index 520dfa10e87..8f2970f5324 100644 --- a/packages/editor/src/ce/extensions/rich-text/extensions.tsx +++ b/packages/editor/src/ce/extensions/rich-text/extensions.tsx @@ -6,7 +6,7 @@ import { IEditorProps, TExtensions } from "@/types"; export type TRichTextEditorAdditionalExtensionsProps = Pick< IEditorProps, - "disabledExtensions" | "flaggedExtensions" | "fileHandler" + "disabledExtensions" | "flaggedExtensions" | "fileHandler" | "isTouchDevice" >; /** diff --git a/packages/editor/src/ce/extensions/rich-text/read-only-extensions.tsx b/packages/editor/src/ce/extensions/rich-text/read-only-extensions.tsx deleted file mode 100644 index 0b7cbc7306d..00000000000 --- a/packages/editor/src/ce/extensions/rich-text/read-only-extensions.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { AnyExtension, Extensions } from "@tiptap/core"; -// types -import { IReadOnlyEditorProps, TExtensions } from "@/types"; - -export type TRichTextReadOnlyEditorAdditionalExtensionsProps = Pick< - IReadOnlyEditorProps, - "disabledExtensions" | "flaggedExtensions" | "fileHandler" ->; - -/** - * Registry entry configuration for extensions - */ -export type TRichTextReadOnlyEditorAdditionalExtensionsRegistry = { - /** Determines if the extension should be enabled based on disabled extensions */ - isEnabled: (disabledExtensions: TExtensions[]) => boolean; - /** Returns the extension instance(s) when enabled */ - getExtension: (props: TRichTextReadOnlyEditorAdditionalExtensionsProps) => AnyExtension | undefined; -}; - -const extensionRegistry: TRichTextReadOnlyEditorAdditionalExtensionsRegistry[] = []; - -export const RichTextReadOnlyEditorAdditionalExtensions = (props: TRichTextReadOnlyEditorAdditionalExtensionsProps) => { - const { disabledExtensions } = props; - - const extensions: Extensions = extensionRegistry - .filter((config) => config.isEnabled(disabledExtensions)) - .map((config) => config.getExtension(props)) - .filter((extension): extension is AnyExtension => extension !== undefined); - - return extensions; -}; 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 daaa5617737..1d099fe3f74 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -30,6 +30,7 @@ const CollaborativeDocumentEditor: React.FC = forwardedRef, handleEditorReady, id, + isTouchDevice, mentionHandler, onAssetChange, onChange, @@ -63,6 +64,7 @@ const CollaborativeDocumentEditor: React.FC = forwardedRef, handleEditorReady, id, + isTouchDevice, mentionHandler, onAssetChange, onChange, @@ -90,6 +92,7 @@ const CollaborativeDocumentEditor: React.FC = editor={editor} editorContainerClassName={cn(editorContainerClassNames, "document-editor")} id={id} + isTouchDevice={!!isTouchDevice} isLoading={!hasServerSynced && !hasServerConnectionFailed} tabIndex={tabIndex} /> 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 1316d2d7249..8a82ebaf99a 100644 --- a/packages/editor/src/core/components/editors/document/page-renderer.tsx +++ b/packages/editor/src/core/components/editors/document/page-renderer.tsx @@ -15,12 +15,22 @@ type Props = { editorContainerClassName: string; id: string; isLoading?: boolean; + isTouchDevice: boolean; tabIndex?: number; }; export const PageRenderer = (props: Props) => { - const { aiHandler, bubbleMenuEnabled, displayConfig, editor, editorContainerClassName, id, isLoading, tabIndex } = - props; + const { + aiHandler, + bubbleMenuEnabled, + displayConfig, + editor, + editorContainerClassName, + id, + isLoading, + isTouchDevice, + tabIndex, + } = props; return (
{ editor={editor} editorContainerClassName={editorContainerClassName} id={id} + isTouchDevice={isTouchDevice} > - {editor.isEditable && ( + {editor.isEditable && !isTouchDevice && (
{bubbleMenuEnabled && } diff --git a/packages/editor/src/core/components/editors/document/read-only-editor.tsx b/packages/editor/src/core/components/editors/document/read-only-editor.tsx index 8f0d67ddc0d..ea246f19402 100644 --- a/packages/editor/src/core/components/editors/document/read-only-editor.tsx +++ b/packages/editor/src/core/components/editors/document/read-only-editor.tsx @@ -25,6 +25,7 @@ const DocumentReadOnlyEditor: React.FC = (props) = fileHandler, flaggedExtensions, id, + isTouchDevice, forwardedRef, handleEditorReady, initialValue, @@ -64,6 +65,7 @@ const DocumentReadOnlyEditor: React.FC = (props) = editor={editor} editorContainerClassName={cn(editorContainerClassName, "document-editor")} id={id} + isTouchDevice={!!isTouchDevice} /> ); }; diff --git a/packages/editor/src/core/components/editors/editor-container.tsx b/packages/editor/src/core/components/editors/editor-container.tsx index f189bde9850..bf8bf36579c 100644 --- a/packages/editor/src/core/components/editors/editor-container.tsx +++ b/packages/editor/src/core/components/editors/editor-container.tsx @@ -1,4 +1,4 @@ -import { Editor } from "@tiptap/react"; +import type { Editor } from "@tiptap/react"; import { FC, ReactNode, useRef } from "react"; // plane utils import { cn } from "@plane/utils"; @@ -10,16 +10,18 @@ import { TDisplayConfig } from "@/types"; // components import { LinkViewContainer } from "./link-view-container"; -interface EditorContainerProps { +type Props = { children: ReactNode; displayConfig: TDisplayConfig; editor: Editor; editorContainerClassName: string; id: string; -} + isTouchDevice: boolean; +}; -export const EditorContainer: FC = (props) => { - const { children, displayConfig, editor, editorContainerClassName, id } = props; +export const EditorContainer: FC = (props) => { + const { children, displayConfig, editor, editorContainerClassName, id, isTouchDevice } = props; + // refs const containerRef = useRef(null); const handleContainerClick = (event: React.MouseEvent) => { @@ -94,7 +96,7 @@ export const EditorContainer: FC = (props) => { )} > {children} - + {!isTouchDevice && }
); diff --git a/packages/editor/src/core/components/editors/editor-wrapper.tsx b/packages/editor/src/core/components/editors/editor-wrapper.tsx index 0ca626683dc..c564883b826 100644 --- a/packages/editor/src/core/components/editors/editor-wrapper.tsx +++ b/packages/editor/src/core/components/editors/editor-wrapper.tsx @@ -27,6 +27,7 @@ export const EditorWrapper: React.FC = (props) => { extensions, id, initialValue, + isTouchDevice, fileHandler, flaggedExtensions, forwardedRef, @@ -75,6 +76,7 @@ export const EditorWrapper: React.FC = (props) => { editor={editor} editorContainerClassName={editorContainerClassName} id={id} + isTouchDevice={!!isTouchDevice} > {children?.(editor)}
diff --git a/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx b/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx index b6abd1a6a50..b9773448c17 100644 --- a/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx +++ b/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx @@ -21,6 +21,7 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => { forwardedRef, id, initialValue, + isTouchDevice, mentionHandler, } = props; @@ -47,6 +48,7 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => { editor={editor} editorContainerClassName={editorContainerClassName} id={id} + isTouchDevice={!!isTouchDevice} >
diff --git a/packages/editor/src/core/components/editors/rich-text/editor.tsx b/packages/editor/src/core/components/editors/rich-text/editor.tsx index 8544dcc8323..6886eb1381c 100644 --- a/packages/editor/src/core/components/editors/rich-text/editor.tsx +++ b/packages/editor/src/core/components/editors/rich-text/editor.tsx @@ -17,6 +17,7 @@ const RichTextEditor: React.FC = (props) => { extensions: externalExtensions = [], fileHandler, flaggedExtensions, + isTouchDevice = false, } = props; const getExtensions = useCallback(() => { @@ -30,6 +31,7 @@ const RichTextEditor: React.FC = (props) => { disabledExtensions, fileHandler, flaggedExtensions, + isTouchDevice, }), ]; diff --git a/packages/editor/src/core/extensions/custom-image/components/block.tsx b/packages/editor/src/core/extensions/custom-image/components/block.tsx index 45c66f428bd..09852f4b1bf 100644 --- a/packages/editor/src/core/extensions/custom-image/components/block.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/block.tsx @@ -57,6 +57,8 @@ export const CustomImageBlock: React.FC = (props) => { const imageRef = useRef(null); const [hasErroredOnFirstLoad, setHasErroredOnFirstLoad] = useState(false); const [hasTriedRestoringImageOnce, setHasTriedRestoringImageOnce] = useState(false); + // extension options + const isTouchDevice = extension.options.isTouchDevice; const updateAttributesSafely = useCallback( (attributes: Partial, errorMessage: string) => { @@ -188,11 +190,15 @@ export const CustomImageBlock: React.FC = (props) => { const handleImageMouseDown = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); + if (isTouchDevice) { + e.preventDefault(); + editor.commands.blur(); + } const pos = getPos(); const nodeSelection = NodeSelection.create(editor.state.doc, pos); editor.view.dispatch(editor.state.tr.setSelection(nodeSelection)); }, - [editor, getPos] + [editor, getPos, isTouchDevice] ); // show the image loader if the remote image's src or preview image from filesystem is not set yet (while loading the image post upload) (or) @@ -280,14 +286,15 @@ export const CustomImageBlock: React.FC = (props) => { {showImageToolbar && ( updateAttributesSafely({ alignment }, "Failed to update attributes while changing alignment:") } + height={size.height} + width={size.width} + src={resolvedImageSrc} /> )} {selected && displayedImageSrc === resolvedImageSrc && ( diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/modal.tsx b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/modal.tsx index 9a30908c2c1..05b262d7597 100644 --- a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/modal.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/modal.tsx @@ -11,15 +11,16 @@ const ZOOM_STEPS = [0.5, 1, 1.5, 2]; type Props = { aspectRatio: number; - isFullScreenEnabled: boolean; downloadSrc: string; + isFullScreenEnabled: boolean; + isTouchDevice: boolean; src: string; toggleFullScreenMode: (val: boolean) => void; width: string; }; const ImageFullScreenModalWithoutPortal = (props: Props) => { - const { aspectRatio, isFullScreenEnabled, downloadSrc, src, toggleFullScreenMode, width } = props; + const { aspectRatio, isFullScreenEnabled, isTouchDevice, downloadSrc, src, toggleFullScreenMode, width } = props; // refs const dragStart = useRef({ x: 0, y: 0 }); const dragOffset = useRef({ x: 0, y: 0 }); @@ -251,22 +252,26 @@ const ImageFullScreenModalWithoutPortal = (props: Props) => {
- - + {!isTouchDevice && ( + + )} + {!isTouchDevice && ( + + )}
diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/root.tsx b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/root.tsx index 2108bfeaaee..7056c420355 100644 --- a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/root.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen/root.tsx @@ -2,26 +2,30 @@ import { Maximize } from "lucide-react"; import { useEffect, useState } from "react"; // plane imports import { Tooltip } from "@plane/ui"; +// extensions +import { CustomImageExtensionOptions } from "@/extensions/custom-image/types"; // local imports import { ImageFullScreenModal } from "./modal"; type Props = { + extensionOptions: CustomImageExtensionOptions; image: { + aspectRatio: number; downloadSrc: string; - src: string; height: string; + src: string; width: string; - aspectRatio: number; }; toggleToolbarViewStatus: (val: boolean) => void; }; export const ImageFullScreenActionRoot: React.FC = (props) => { - const { image, toggleToolbarViewStatus } = props; + const { extensionOptions, image, toggleToolbarViewStatus } = props; // states const [isFullScreenEnabled, setIsFullScreenEnabled] = useState(false); // derived values const { downloadSrc, src, width, aspectRatio } = image; + const { isTouchDevice } = extensionOptions; useEffect(() => { toggleToolbarViewStatus(isFullScreenEnabled); @@ -31,13 +35,14 @@ export const ImageFullScreenActionRoot: React.FC = (props) => { <> - +