[WIKI-539] refactor: remove lite text read only editor (#7481)

* refactor: remove lite text read only editor

* chore: update types
This commit is contained in:
Aaryan Khandelwal 2025-07-29 18:09:39 +05:30 committed by GitHub
parent 55f06cf546
commit e0fa6553ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 280 additions and 758 deletions

View file

@ -3,12 +3,12 @@ import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { Globe2, Lock } from "lucide-react";
// plane imports
import type { EditorReadOnlyRefApi } from "@plane/editor";
import type { EditorRefApi } from "@plane/editor";
import { useHashScroll } from "@plane/hooks";
import { EIssueCommentAccessSpecifier, type TCommentsOperations, type TIssueComment } from "@plane/types";
import { cn } from "@plane/utils";
// components
import { LiteTextReadOnlyEditor } from "@/components/editor";
import { LiteTextEditor } from "@/components/editor/lite-text";
// local imports
import { CommentReactions } from "../comment-reaction";
@ -17,7 +17,7 @@ type Props = {
comment: TIssueComment;
disabled: boolean;
projectId?: string;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
readOnlyEditorRef: React.RefObject<EditorRefApi>;
showAccessSpecifier: boolean;
workspaceId: string;
workspaceSlug: string;
@ -67,7 +67,8 @@ export const CommentCardDisplay: React.FC<Props> = observer((props) => {
)}
</div>
)}
<LiteTextReadOnlyEditor
<LiteTextEditor
editable={false}
ref={readOnlyEditorRef}
id={comment.id}
initialValue={comment.comment_html ?? ""}

View file

@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { useForm } from "react-hook-form";
import { Check, X } from "lucide-react";
// plane imports
import type { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import type { EditorRefApi } from "@plane/editor";
import type { TCommentsOperations, TIssueComment } from "@plane/types";
import { isCommentEmpty } from "@plane/utils";
// components
@ -14,7 +14,7 @@ type Props = {
comment: TIssueComment;
isEditing: boolean;
projectId?: string;
readOnlyEditorRef: EditorReadOnlyRefApi | null;
readOnlyEditorRef: EditorRefApi | null;
setIsEditing: (isEditing: boolean) => void;
workspaceId: string;
workspaceSlug: string;
@ -75,6 +75,7 @@ export const CommentCardEditForm: React.FC<Props> = observer((props) => {
}}
>
<LiteTextEditor
editable
workspaceId={workspaceId}
workspaceSlug={workspaceSlug}
ref={editorRef}

View file

@ -3,7 +3,7 @@
import { FC, useRef, useState } from "react";
import { observer } from "mobx-react";
// plane imports
import type { EditorReadOnlyRefApi } from "@plane/editor";
import type { EditorRefApi } from "@plane/editor";
import type { TIssueComment, TCommentsOperations } from "@plane/types";
// plane web imports
import { CommentBlock } from "@/plane-web/components/comments";
@ -34,9 +34,10 @@ export const CommentCard: FC<TCommentCard> = observer((props) => {
disabled = false,
projectId,
} = props;
const readOnlyEditorRef = useRef<EditorReadOnlyRefApi>(null);
// states
const [isEditing, setIsEditing] = useState(false);
// refs
const readOnlyEditorRef = useRef<EditorRefApi>(null);
// derived values
const workspaceId = comment?.workspace;

View file

@ -113,6 +113,7 @@ export const CommentCreate: FC<TCommentCreate> = observer((props) => {
control={control}
render={({ field: { value, onChange } }) => (
<LiteTextEditor
editable
workspaceId={workspaceId}
id={"add_comment_" + entityId}
value={"<p></p>"}

View file

@ -1,18 +1,14 @@
import React, { useState } from "react";
// plane constants
// plane imports
import { EIssueCommentAccessSpecifier } from "@plane/constants";
// plane editor
import { EditorRefApi, ILiteTextEditorProps, LiteTextEditorWithRef, TFileHandler } from "@plane/editor";
// i18n
import { type EditorRefApi, type ILiteTextEditorProps, LiteTextEditorWithRef, type TFileHandler } from "@plane/editor";
import { useTranslation } from "@plane/i18n";
// components
import { MakeOptional } from "@plane/types";
import type { MakeOptional } from "@plane/types";
import { cn, isCommentEmpty } from "@plane/utils";
// components
import { EditorMentionsRoot, IssueCommentToolbar } from "@/components/editor";
// helpers
// hooks
import { useEditorConfig, useEditorMention } from "@/hooks/editor";
// store hooks
import { useMember } from "@/hooks/store";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
@ -20,11 +16,10 @@ import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
import { WorkspaceService } from "@/plane-web/services";
const workspaceService = new WorkspaceService();
interface LiteTextEditorWrapperProps
extends MakeOptional<
Omit<ILiteTextEditorProps, "fileHandler" | "mentionHandler">,
"disabledExtensions" | "flaggedExtensions"
> {
type LiteTextEditorWrapperProps = MakeOptional<
Omit<ILiteTextEditorProps, "fileHandler" | "mentionHandler">,
"disabledExtensions" | "flaggedExtensions"
> & {
workspaceSlug: string;
workspaceId: string;
projectId?: string;
@ -35,15 +30,23 @@ interface LiteTextEditorWrapperProps
isSubmitting?: boolean;
showToolbarInitially?: boolean;
showToolbar?: boolean;
uploadFile: TFileHandler["upload"];
issue_id?: string;
parentClassName?: string;
}
} & (
| {
editable: false;
}
| {
editable: true;
uploadFile: TFileHandler["upload"];
}
);
export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapperProps>((props, ref) => {
const { t } = useTranslation();
const {
containerClassName,
editable,
workspaceSlug,
workspaceId,
projectId,
@ -57,7 +60,6 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
showToolbar = true,
parentClassName = "",
placeholder = t("issue.comments.placeholder"),
uploadFile,
disabledExtensions: additionalDisabledExtensions = [],
...rest
} = props;
@ -70,10 +72,10 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
// use editor mention
const { fetchMentions } = useEditorMention({
searchEntity: async (payload) =>
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
await workspaceService.searchEntity(workspaceSlug, {
...payload,
project_id: projectId?.toString() ?? "",
issue_id: issue_id,
project_id: projectId,
issue_id,
}),
});
// editor config
@ -94,10 +96,11 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
<LiteTextEditorWithRef
ref={ref}
disabledExtensions={[...liteTextEditorExtensions.disabled, ...additionalDisabledExtensions]}
editable={editable}
flaggedExtensions={liteTextEditorExtensions.flagged}
fileHandler={getEditorFileHandlers({
projectId,
uploadFile,
uploadFile: editable ? props.uploadFile : async () => "",
workspaceId,
workspaceSlug,
})}
@ -107,8 +110,10 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
if (!res) throw new Error("Failed in fetching mentions");
return res;
},
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
renderComponent: EditorMentionsRoot,
getMentionedEntityDetails: (id) => ({
display_name: getUserDetails(id)?.display_name ?? "",
}),
}}
placeholder={placeholder}
containerClassName={cn(containerClassName, "relative")}

View file

@ -1,3 +1,2 @@
export * from "./editor";
export * from "./read-only-editor";
export * from "./toolbar";

View file

@ -1,57 +0,0 @@
import React from "react";
// plane imports
import { EditorReadOnlyRefApi, ILiteTextReadOnlyEditorProps, LiteTextReadOnlyEditorWithRef } 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 LiteTextReadOnlyEditorWrapperProps = MakeOptional<
Omit<ILiteTextReadOnlyEditorProps, "fileHandler" | "mentionHandler">,
"disabledExtensions" | "flaggedExtensions"
> & {
workspaceId: string;
workspaceSlug: string;
projectId?: string;
};
export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, LiteTextReadOnlyEditorWrapperProps>(
({ workspaceId, workspaceSlug, projectId, disabledExtensions: additionalDisabledExtensions, ...props }, ref) => {
// store hooks
const { getUserDetails } = useMember();
// editor flaggings
const { liteText: liteTextEditorExtensions } = useEditorFlagging(workspaceSlug?.toString());
// editor config
const { getReadOnlyEditorFileHandlers } = useEditorConfig();
return (
<LiteTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={[...liteTextEditorExtensions.disabled, ...(additionalDisabledExtensions ?? [])]}
flaggedExtensions={liteTextEditorExtensions.flagged}
fileHandler={getReadOnlyEditorFileHandlers({
projectId,
workspaceId,
workspaceSlug,
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
}}
{...props}
// overriding the containerClassName to add relative class passed
containerClassName={cn(props.containerClassName, "relative p-2")}
/>
);
}
);
LiteTextReadOnlyEditor.displayName = "LiteTextReadOnlyEditor";

View file

@ -1,14 +1,12 @@
import React, { forwardRef } from "react";
// plane imports
import { EditorRefApi, IRichTextEditorProps, RichTextEditorWithRef, TFileHandler } from "@plane/editor";
import { MakeOptional, TSearchEntityRequestPayload, TSearchResponse } from "@plane/types";
// components
import { type EditorRefApi, type IRichTextEditorProps, RichTextEditorWithRef, type TFileHandler } from "@plane/editor";
import type { MakeOptional, TSearchEntityRequestPayload, TSearchResponse } from "@plane/types";
import { cn } from "@plane/utils";
// components
import { EditorMentionsRoot } from "@/components/editor";
// helpers
// hooks
import { useEditorConfig, useEditorMention } from "@/hooks/editor";
// store hooks
import { useMember } from "@/hooks/store";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
@ -38,7 +36,7 @@ export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProp
workspaceSlug,
workspaceId,
projectId,
disabledExtensions: additionalDisabledExtensions,
disabledExtensions: additionalDisabledExtensions = [],
...rest
} = props;
// store hooks
@ -70,8 +68,10 @@ export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProp
if (!res) throw new Error("Failed in fetching mentions");
return res;
},
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
renderComponent: EditorMentionsRoot,
getMentionedEntityDetails: (id) => ({
display_name: getUserDetails(id)?.display_name ?? "",
}),
}}
{...rest}
containerClassName={cn("relative pl-3 pb-3", containerClassName)}

View file

@ -14,7 +14,10 @@ import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
import { StickyEditorToolbar } from "./toolbar";
interface StickyEditorWrapperProps
extends Omit<ILiteTextEditorProps, "disabledExtensions" | "flaggedExtensions" | "fileHandler" | "mentionHandler"> {
extends Omit<
ILiteTextEditorProps,
"disabledExtensions" | "editable" | "flaggedExtensions" | "fileHandler" | "mentionHandler"
> {
workspaceSlug: string;
workspaceId: string;
projectId?: string;
@ -67,6 +70,7 @@ export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperPr
ref={ref}
disabledExtensions={[...liteTextEditorExtensions.disabled, "enter-key"]}
flaggedExtensions={liteTextEditorExtensions.flagged}
editable
fileHandler={getEditorFileHandlers({
projectId,
uploadFile,

View file

@ -1,7 +1,6 @@
import { useCallback } from "react";
// plane editor
import { TFileHandler, TReadOnlyFileHandler } from "@plane/editor";
// helpers
// plane imports
import type { TFileHandler } from "@plane/editor";
import { getEditorAssetDownloadSrc, getEditorAssetSrc } from "@plane/utils";
// hooks
import { useEditorAsset } from "@/hooks/store";
@ -24,15 +23,30 @@ export const useEditorConfig = () => {
// file size
const { maxFileSize } = useFileSize();
const getReadOnlyEditorFileHandlers = useCallback(
(args: Pick<TArgs, "projectId" | "workspaceId" | "workspaceSlug">): TReadOnlyFileHandler => {
const { projectId, workspaceId, workspaceSlug } = args;
const getEditorFileHandlers = useCallback(
(args: TArgs): TFileHandler => {
const { projectId, uploadFile, workspaceId, workspaceSlug } = args;
return {
assetsUploadStatus: assetsUploadPercentage,
cancel: fileService.cancelUpload,
checkIfAssetExists: async (assetId: string) => {
const res = await fileService.checkIfAssetExists(workspaceSlug, assetId);
return res?.exists ?? false;
},
delete: async (src: string) => {
if (src?.startsWith("http")) {
await fileService.deleteOldWorkspaceAsset(workspaceId, src);
} else {
await fileService.deleteNewAsset(
getEditorAssetSrc({
assetId: src,
projectId,
workspaceSlug,
}) ?? ""
);
}
},
getAssetDownloadSrc: async (path) => {
if (!path) return "";
if (path?.startsWith("http")) {
@ -68,47 +82,16 @@ export const useEditorConfig = () => {
await fileService.restoreNewAsset(workspaceSlug, src);
}
},
};
},
[]
);
const getEditorFileHandlers = useCallback(
(args: TArgs): TFileHandler => {
const { projectId, uploadFile, workspaceId, workspaceSlug } = args;
return {
...getReadOnlyEditorFileHandlers({
projectId,
workspaceId,
workspaceSlug,
}),
assetsUploadStatus: assetsUploadPercentage,
upload: uploadFile,
delete: async (src: string) => {
if (src?.startsWith("http")) {
await fileService.deleteOldWorkspaceAsset(workspaceId, src);
} else {
await fileService.deleteNewAsset(
getEditorAssetSrc({
assetId: src,
projectId,
workspaceSlug,
}) ?? ""
);
}
},
cancel: fileService.cancelUpload,
validation: {
maxFileSize,
},
};
},
[assetsUploadPercentage, getReadOnlyEditorFileHandlers, maxFileSize]
[assetsUploadPercentage, maxFileSize]
);
return {
getEditorFileHandlers,
getReadOnlyEditorFileHandlers,
};
};