From 6f27ec031d3ce481d1587ab0fd35ff5caeeb48c7 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:16:17 +0530 Subject: [PATCH] [WIKI-466] refactor: remove rich text read only editor (#7241) * refactor: remove rich text read only editor * fix: type imports --- apps/space/core/components/editor/index.ts | 2 +- .../components/editor/rich-text-editor.tsx | 25 ++-- .../editor/rich-text-read-only-editor.tsx | 48 -------- .../issues/peek-overview/issue-details.tsx | 5 +- .../pages/editor/ai/ask-pi-menu.tsx | 5 +- .../ce/components/pages/editor/ai/menu.tsx | 5 +- .../core/description-versions/modal.tsx | 9 +- .../core/modals/gpt-assistant-popover.tsx | 17 +-- .../editor/rich-text-editor/index.ts | 1 - .../rich-text-editor/rich-text-editor.tsx | 30 +++-- .../rich-text-read-only-editor.tsx | 59 --------- .../modals/create-modal/issue-description.tsx | 1 + .../components/issues/description-input.tsx | 113 ++++++++---------- .../components/description-editor.tsx | 7 +- .../profile/activity/activity-list.tsx | 5 +- .../activity/profile-activity-list.tsx | 7 +- .../components/editors/editor-wrapper.tsx | 4 +- .../components/editors/lite-text/editor.tsx | 2 +- .../components/editors/rich-text/index.ts | 1 - .../editors/rich-text/read-only-editor.tsx | 33 ----- packages/editor/src/core/types/editor.ts | 8 +- packages/editor/src/index.ts | 1 - 22 files changed, 126 insertions(+), 262 deletions(-) delete mode 100644 apps/space/core/components/editor/rich-text-read-only-editor.tsx delete mode 100644 apps/web/core/components/editor/rich-text-editor/rich-text-read-only-editor.tsx delete mode 100644 packages/editor/src/core/components/editors/rich-text/read-only-editor.tsx diff --git a/apps/space/core/components/editor/index.ts b/apps/space/core/components/editor/index.ts index 894daf2241..de164c8376 100644 --- a/apps/space/core/components/editor/index.ts +++ b/apps/space/core/components/editor/index.ts @@ -1,5 +1,5 @@ export * from "./embeds"; export * from "./lite-text-editor"; export * from "./lite-text-read-only-editor"; -export * from "./rich-text-read-only-editor"; +export * from "./rich-text-editor"; export * from "./toolbar"; diff --git a/apps/space/core/components/editor/rich-text-editor.tsx b/apps/space/core/components/editor/rich-text-editor.tsx index 63320cd2c7..7a9178f65b 100644 --- a/apps/space/core/components/editor/rich-text-editor.tsx +++ b/apps/space/core/components/editor/rich-text-editor.tsx @@ -9,18 +9,24 @@ import { getEditorFileHandlers } from "@/helpers/editor.helper"; // store hooks import { useMember } from "@/hooks/store"; -interface RichTextEditorWrapperProps - extends MakeOptional< - Omit, - "disabledExtensions" | "flaggedExtensions" - > { +type RichTextEditorWrapperProps = MakeOptional< + Omit, + "disabledExtensions" | "flaggedExtensions" +> & { anchor: string; - uploadFile: TFileHandler["upload"]; workspaceId: string; -} +} & ( + | { + editable: false; + } + | { + editable: true; + uploadFile: TFileHandler["upload"]; + } + ); export const RichTextEditor = forwardRef((props, ref) => { - const { anchor, containerClassName, uploadFile, workspaceId, disabledExtensions, flaggedExtensions, ...rest } = props; + const { anchor, containerClassName, editable, workspaceId, disabledExtensions, flaggedExtensions, ...rest } = props; const { getMemberById } = useMember(); return ( "", workspaceId, })} flaggedExtensions={flaggedExtensions ?? []} diff --git a/apps/space/core/components/editor/rich-text-read-only-editor.tsx b/apps/space/core/components/editor/rich-text-read-only-editor.tsx deleted file mode 100644 index dd9d371901..0000000000 --- a/apps/space/core/components/editor/rich-text-read-only-editor.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; -// plane imports -import { EditorReadOnlyRefApi, IRichTextReadOnlyEditorProps, RichTextReadOnlyEditorWithRef } from "@plane/editor"; -import { MakeOptional } from "@plane/types"; -import { cn } from "@plane/utils"; -// components -import { EditorMentionsRoot } from "@/components/editor"; -// helpers -import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper"; -// store hooks -import { useMember } from "@/hooks/store"; - -type RichTextReadOnlyEditorWrapperProps = MakeOptional< - Omit, - "disabledExtensions" | "flaggedExtensions" -> & { - anchor: string; - workspaceId: string; -}; - -export const RichTextReadOnlyEditor = React.forwardRef( - ({ anchor, workspaceId, disabledExtensions, flaggedExtensions, ...props }, ref) => { - const { getMemberById } = useMember(); - - return ( - , - getMentionedEntityDetails: (id: string) => ({ - display_name: getMemberById(id)?.member__display_name ?? "", - }), - }} - {...props} - // overriding the customClassName to add relative class passed - containerClassName={cn("relative p-0 border-none", props.containerClassName)} - /> - ); - } -); - -RichTextReadOnlyEditor.displayName = "RichTextReadOnlyEditor"; diff --git a/apps/space/core/components/issues/peek-overview/issue-details.tsx b/apps/space/core/components/issues/peek-overview/issue-details.tsx index 32bcbc9bdd..ad79dfde6e 100644 --- a/apps/space/core/components/issues/peek-overview/issue-details.tsx +++ b/apps/space/core/components/issues/peek-overview/issue-details.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react"; // components -import { RichTextReadOnlyEditor } from "@/components/editor"; +import { RichTextEditor } from "@/components/editor"; import { IssueReactions } from "@/components/issues/peek-overview"; import { usePublish } from "@/hooks/store"; // types @@ -25,7 +25,8 @@ export const PeekOverviewIssueDetails: React.FC = observer((props) => {

{issueDetails.name}

{description !== "" && description !== "

" && ( - = (props) => { {response ? (
- = (props) => { {response ? (
- = observer((props) => { workspaceSlug, } = props; // refs - const editorRef = useRef(null); + const editorRef = useRef(null); // store hooks const { getUserDetails } = useMember(); const { getWorkspaceBySlug } = useWorkspace(); @@ -131,7 +131,8 @@ export const DescriptionVersionsModal: React.FC = observer((props) => { {/* Version description */}
{activeVersionDescription ? ( - = (props) => { const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); // refs - const editorRef = useRef(null); - const responseRef = useRef(null); + const editorRef = useRef(null); + const responseRef = useRef(null); // popper const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: placement ?? "auto", @@ -217,7 +216,8 @@ export const GptAssistantPopover: React.FC = (props) => { {prompt && (
Content: - = (props) => { {response !== "" && (
Response: - ${response}

`} ref={responseRef} diff --git a/apps/web/core/components/editor/rich-text-editor/index.ts b/apps/web/core/components/editor/rich-text-editor/index.ts index f185d0054e..49fdb69dd1 100644 --- a/apps/web/core/components/editor/rich-text-editor/index.ts +++ b/apps/web/core/components/editor/rich-text-editor/index.ts @@ -1,2 +1 @@ export * from "./rich-text-editor"; -export * from "./rich-text-read-only-editor"; diff --git a/apps/web/core/components/editor/rich-text-editor/rich-text-editor.tsx b/apps/web/core/components/editor/rich-text-editor/rich-text-editor.tsx index fdb5a3bfd5..7349bfe48d 100644 --- a/apps/web/core/components/editor/rich-text-editor/rich-text-editor.tsx +++ b/apps/web/core/components/editor/rich-text-editor/rich-text-editor.tsx @@ -13,26 +13,31 @@ import { useMember } from "@/hooks/store"; // plane web hooks import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging"; -interface RichTextEditorWrapperProps - extends MakeOptional< - Omit, - "disabledExtensions" | "flaggedExtensions" - > { - searchMentionCallback: (payload: TSearchEntityRequestPayload) => Promise; +type RichTextEditorWrapperProps = MakeOptional< + Omit, + "disabledExtensions" | "editable" | "flaggedExtensions" +> & { workspaceSlug: string; workspaceId: string; projectId?: string; - uploadFile: TFileHandler["upload"]; -} +} & ( + | { + editable: false; + } + | { + editable: true; + searchMentionCallback: (payload: TSearchEntityRequestPayload) => Promise; + uploadFile: TFileHandler["upload"]; + } + ); export const RichTextEditor = forwardRef((props, ref) => { const { containerClassName, + editable, workspaceSlug, workspaceId, projectId, - searchMentionCallback, - uploadFile, disabledExtensions: additionalDisabledExtensions, ...rest } = props; @@ -42,7 +47,7 @@ export const RichTextEditor = forwardRef await searchMentionCallback(payload), + searchEntity: editable ? async (payload) => await props.searchMentionCallback(payload) : async () => ({}), }); // editor config const { getEditorFileHandlers } = useEditorConfig(); @@ -51,10 +56,11 @@ export const RichTextEditor = forwardRef "", workspaceId, workspaceSlug, })} diff --git a/apps/web/core/components/editor/rich-text-editor/rich-text-read-only-editor.tsx b/apps/web/core/components/editor/rich-text-editor/rich-text-read-only-editor.tsx deleted file mode 100644 index b89804e015..0000000000 --- a/apps/web/core/components/editor/rich-text-editor/rich-text-read-only-editor.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client"; - -import React from "react"; -// plane imports -import { EditorReadOnlyRefApi, IRichTextReadOnlyEditorProps, RichTextReadOnlyEditorWithRef } from "@plane/editor"; -import { MakeOptional } from "@plane/types"; -// components -import { cn } from "@plane/utils"; -import { EditorMentionsRoot } from "@/components/editor"; -// helpers -// hooks -import { useEditorConfig } from "@/hooks/editor"; -// store hooks -import { useMember } from "@/hooks/store"; -// plane web hooks -import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging"; - -type RichTextReadOnlyEditorWrapperProps = MakeOptional< - Omit, - "disabledExtensions" | "flaggedExtensions" -> & { - workspaceId: string; - workspaceSlug: string; - projectId?: string; -}; - -export const RichTextReadOnlyEditor = React.forwardRef( - ({ workspaceId, workspaceSlug, projectId, disabledExtensions: additionalDisabledExtensions, ...props }, ref) => { - // store hooks - const { getUserDetails } = useMember(); - - // editor flaggings - const { richText: richTextEditorExtensions } = useEditorFlagging(workspaceSlug?.toString()); - // editor config - const { getReadOnlyEditorFileHandlers } = useEditorConfig(); - - return ( - , - getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }), - }} - {...props} - // overriding the containerClassName to add relative class passed - containerClassName={cn(props.containerClassName, "relative pl-3")} - /> - ); - } -); - -RichTextReadOnlyEditor.displayName = "RichTextReadOnlyEditor"; diff --git a/apps/web/core/components/inbox/modals/create-modal/issue-description.tsx b/apps/web/core/components/inbox/modals/create-modal/issue-description.tsx index c116d0cef2..907f48e700 100644 --- a/apps/web/core/components/inbox/modals/create-modal/issue-description.tsx +++ b/apps/web/core/components/inbox/modals/create-modal/issue-description.tsx @@ -62,6 +62,7 @@ export const InboxIssueDescription: FC = observer((props return (

" : data?.description_html} ref={editorRef} diff --git a/apps/web/core/components/issues/description-input.tsx b/apps/web/core/components/issues/description-input.tsx index b8f7b00e97..10d0dfac8b 100644 --- a/apps/web/core/components/issues/description-input.tsx +++ b/apps/web/core/components/issues/description-input.tsx @@ -5,13 +5,13 @@ import debounce from "lodash/debounce"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; // plane imports -import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor"; +import type { EditorRefApi } from "@plane/editor"; import { useTranslation } from "@plane/i18n"; import { EFileAssetType, TIssue, TNameDescriptionLoader } from "@plane/types"; import { Loader } from "@plane/ui"; // components import { getDescriptionPlaceholderI18n } from "@plane/utils"; -import { RichTextEditor, RichTextReadOnlyEditor } from "@/components/editor"; +import { RichTextEditor } from "@/components/editor"; import { TIssueOperations } from "@/components/issues/issue-detail"; // helpers // hooks @@ -22,7 +22,6 @@ const workspaceService = new WorkspaceService(); export type IssueDescriptionInputProps = { containerClassName?: string; - editorReadOnlyRef?: React.RefObject; editorRef?: React.RefObject; workspaceSlug: string; projectId: string; @@ -38,7 +37,6 @@ export type IssueDescriptionInputProps = { export const IssueDescriptionInput: FC = observer((props) => { const { containerClassName, - editorReadOnlyRef, editorRef, workspaceSlug, projectId, @@ -109,66 +107,55 @@ export const IssueDescriptionInput: FC = observer((p - !disabled ? ( -

"} - value={swrIssueDescription ?? null} - workspaceSlug={workspaceSlug} - workspaceId={workspaceId} - projectId={projectId} - dragDropEnabled - onChange={(_description: object, description_html: string) => { - setIsSubmitting("submitting"); - onChange(description_html); - debouncedFormSave(); - }} - placeholder={ - placeholder - ? placeholder - : (isFocused, value) => t(`${getDescriptionPlaceholderI18n(isFocused, value)}`) + render={({ field: { onChange } }) => ( +

"} + value={swrIssueDescription ?? null} + workspaceSlug={workspaceSlug} + workspaceId={workspaceId} + projectId={projectId} + dragDropEnabled + onChange={(_description: object, description_html: string) => { + setIsSubmitting("submitting"); + onChange(description_html); + debouncedFormSave(); + }} + placeholder={ + placeholder + ? placeholder + : (isFocused, value) => t(`${getDescriptionPlaceholderI18n(isFocused, value)}`) + } + searchMentionCallback={async (payload) => + await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { + ...payload, + project_id: projectId?.toString() ?? "", + issue_id: issueId?.toString(), + }) + } + containerClassName={containerClassName} + uploadFile={async (blockId, file) => { + try { + const { asset_id } = await uploadEditorAsset({ + blockId, + data: { + entity_identifier: issueId, + entity_type: EFileAssetType.ISSUE_DESCRIPTION, + }, + file, + projectId, + workspaceSlug, + }); + return asset_id; + } catch (error) { + console.log("Error in uploading work item asset:", error); + throw new Error("Asset upload failed. Please try again later."); } - searchMentionCallback={async (payload) => - await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { - ...payload, - project_id: projectId?.toString() ?? "", - issue_id: issueId?.toString(), - }) - } - containerClassName={containerClassName} - uploadFile={async (blockId, file) => { - try { - const { asset_id } = await uploadEditorAsset({ - blockId, - data: { - entity_identifier: issueId, - entity_type: EFileAssetType.ISSUE_DESCRIPTION, - }, - file, - projectId, - workspaceSlug, - }); - return asset_id; - } catch (error) { - console.log("Error in uploading work item asset:", error); - throw new Error("Asset upload failed. Please try again later."); - } - }} - ref={editorRef} - /> - ) : ( - - ) - } + }} + ref={editorRef} + /> + )} /> ) : ( diff --git a/apps/web/core/components/issues/issue-modal/components/description-editor.tsx b/apps/web/core/components/issues/issue-modal/components/description-editor.tsx index 2540756a87..8e0bc68f53 100644 --- a/apps/web/core/components/issues/issue-modal/components/description-editor.tsx +++ b/apps/web/core/components/issues/issue-modal/components/description-editor.tsx @@ -6,13 +6,9 @@ import { Control, Controller } from "react-hook-form"; import { Sparkle } from "lucide-react"; // plane imports import { ETabIndices } from "@plane/constants"; -// editor -import { EditorRefApi } from "@plane/editor"; -// i18n +import type { EditorRefApi } from "@plane/editor"; import { useTranslation } from "@plane/i18n"; -// types import { EFileAssetType, TIssue } from "@plane/types"; -// ui import { Loader, setToast, TOAST_TYPE } from "@plane/ui"; import { getDescriptionPlaceholderI18n, getTabIndex } from "@plane/utils"; // components @@ -177,6 +173,7 @@ export const IssueDescriptionEditor: React.FC = ob control={control} render={({ field: { value, onChange } }) => ( = observer((props) => {

- = observer((props) => {

- React.ReactNode; + editable: boolean; extensions: Extensions; }; @@ -21,6 +22,7 @@ export const EditorWrapper: React.FC = (props) => { containerClassName, disabledExtensions, displayConfig = DEFAULT_DISPLAY_CONFIG, + editable, editorClassName = "", extensions, id, @@ -39,7 +41,7 @@ export const EditorWrapper: React.FC = (props) => { } = props; const editor = useEditor({ - editable: true, + editable, disabledExtensions, editorClassName, enableHistory: true, diff --git a/packages/editor/src/core/components/editors/lite-text/editor.tsx b/packages/editor/src/core/components/editors/lite-text/editor.tsx index df89521ae6..66913057b8 100644 --- a/packages/editor/src/core/components/editors/lite-text/editor.tsx +++ b/packages/editor/src/core/components/editors/lite-text/editor.tsx @@ -19,7 +19,7 @@ const LiteTextEditor: React.FC = (props) => { return resolvedExtensions; }, [externalExtensions, disabledExtensions, onEnterKeyPress]); - return ; + return ; }; const LiteTextEditorWithRef = forwardRef((props, ref) => ( diff --git a/packages/editor/src/core/components/editors/rich-text/index.ts b/packages/editor/src/core/components/editors/rich-text/index.ts index b2ba8682a3..8b1fd904bb 100644 --- a/packages/editor/src/core/components/editors/rich-text/index.ts +++ b/packages/editor/src/core/components/editors/rich-text/index.ts @@ -1,2 +1 @@ export * from "./editor"; -export * from "./read-only-editor"; diff --git a/packages/editor/src/core/components/editors/rich-text/read-only-editor.tsx b/packages/editor/src/core/components/editors/rich-text/read-only-editor.tsx deleted file mode 100644 index efad3d6ac1..0000000000 --- a/packages/editor/src/core/components/editors/rich-text/read-only-editor.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { forwardRef, useCallback } from "react"; -// plane editor extensions -import { RichTextReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/read-only-extensions"; -// types -import { EditorReadOnlyRefApi, IRichTextReadOnlyEditorProps } from "@/types"; -// local imports -import { ReadOnlyEditorWrapper } from "../read-only-editor-wrapper"; - -const RichTextReadOnlyEditorWithRef = forwardRef((props, ref) => { - const { disabledExtensions, fileHandler, flaggedExtensions } = props; - - const getExtensions = useCallback(() => { - const extensions = RichTextReadOnlyEditorAdditionalExtensions({ - disabledExtensions, - fileHandler, - flaggedExtensions, - }); - - return extensions; - }, [disabledExtensions, fileHandler, flaggedExtensions]); - - return ( - } - /> - ); -}); - -RichTextReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef"; - -export { RichTextReadOnlyEditorWithRef }; diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 129e8cd0b1..d4d572502f 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -143,9 +143,11 @@ export interface IEditorProps { } export type ILiteTextEditorProps = IEditorProps; -export interface IRichTextEditorProps extends IEditorProps { + +export type IRichTextEditorProps = IEditorProps & { dragDropEnabled?: boolean; -} + editable: boolean; +}; export interface ICollaborativeDocumentEditorProps extends Omit { @@ -178,8 +180,6 @@ export interface IReadOnlyEditorProps export type ILiteTextReadOnlyEditorProps = IReadOnlyEditorProps; -export type IRichTextReadOnlyEditorProps = IReadOnlyEditorProps; - export interface IDocumentReadOnlyEditorProps extends IReadOnlyEditorProps { embedHandler: TEmbedConfig; } diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 43b295647a..5484d0affa 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -13,7 +13,6 @@ export { LiteTextEditorWithRef, LiteTextReadOnlyEditorWithRef, RichTextEditorWithRef, - RichTextReadOnlyEditorWithRef, } from "@/components/editors"; export { isCellSelection } from "@/extensions/table/table/utilities/is-cell-selection";