From 58ecf9e341294b19781d8ed607694aa51895dc7b Mon Sep 17 00:00:00 2001
From: Aaryan Khandelwal
Date: Fri, 25 Jul 2025 17:42:04 +0530
Subject: [PATCH 1/2] refactor: remove lite text read only editor
---
apps/space/core/components/editor/index.ts | 1 -
.../components/editor/lite-text-editor.tsx | 35 ++--
.../editor/lite-text-read-only-editor.tsx | 48 ------
.../peek-overview/comment/add-comment.tsx | 1 +
.../comment/comment-detail-card.tsx | 6 +-
.../core/components/comments/card/display.tsx | 9 +-
.../components/comments/card/edit-form.tsx | 5 +-
.../core/components/comments/card/root.tsx | 5 +-
.../components/comments/comment-create.tsx | 1 +
.../components/editor/lite-text/editor.tsx | 49 +++---
.../core/components/editor/lite-text/index.ts | 1 -
.../editor/lite-text/read-only-editor.tsx | 57 -------
.../components/editor/rich-text/editor.tsx | 16 +-
.../editor/sticky-editor/editor.tsx | 6 +-
.../core/hooks/editor/use-editor-config.ts | 59 +++----
.../editor/src/ce/extensions/core/index.ts | 1 -
.../extensions/core/read-only-extensions.ts | 15 --
...xtensions.tsx => rich-text-extensions.tsx} | 0
.../rich-text/read-only-extensions.tsx | 31 ----
.../src/core/components/editors/index.ts | 1 -
.../components/editors/lite-text/editor.tsx | 2 +-
.../components/editors/lite-text/index.ts | 1 -
.../editors/lite-text/read-only-editor.tsx | 13 --
.../editors/read-only-editor-wrapper.tsx | 56 ------
.../components/editors/rich-text/editor.tsx | 2 +-
.../src/core/extensions/callout/extension.tsx | 2 +-
.../src/core/extensions/callout/index.ts | 2 -
.../callout/read-only-extension.tsx | 16 --
.../extensions/custom-image/extension.tsx | 4 +-
.../src/core/extensions/image/extension.tsx | 4 +-
packages/editor/src/core/extensions/index.ts | 1 -
.../core/extensions/read-only-extensions.ts | 102 -----------
.../editor/src/core/extensions/utility.ts | 4 +-
.../editor/src/core/helpers/editor-ref.ts | 152 ++++++++++++++++-
packages/editor/src/core/hooks/use-editor.ts | 159 +-----------------
.../src/core/hooks/use-read-only-editor.ts | 69 --------
packages/editor/src/core/plugins/file/root.ts | 4 +-
.../src/core/{props/props.tsx => props.ts} | 4 +-
packages/editor/src/core/props/index.ts | 2 -
packages/editor/src/core/props/read-only.tsx | 18 --
packages/editor/src/core/types/config.ts | 11 +-
packages/editor/src/core/types/editor.ts | 45 +----
packages/editor/src/core/types/hook.ts | 6 +-
packages/editor/src/core/types/mention.ts | 7 +-
packages/editor/src/index.ts | 1 -
45 files changed, 278 insertions(+), 756 deletions(-)
delete mode 100644 apps/space/core/components/editor/lite-text-read-only-editor.tsx
delete mode 100644 apps/web/core/components/editor/lite-text/read-only-editor.tsx
delete mode 100644 packages/editor/src/ce/extensions/core/read-only-extensions.ts
rename packages/editor/src/ce/extensions/{rich-text/extensions.tsx => rich-text-extensions.tsx} (100%)
delete mode 100644 packages/editor/src/ce/extensions/rich-text/read-only-extensions.tsx
delete mode 100644 packages/editor/src/core/components/editors/lite-text/read-only-editor.tsx
delete mode 100644 packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx
delete mode 100644 packages/editor/src/core/extensions/callout/read-only-extension.tsx
delete mode 100644 packages/editor/src/core/extensions/read-only-extensions.ts
delete mode 100644 packages/editor/src/core/hooks/use-read-only-editor.ts
rename packages/editor/src/core/{props/props.tsx => props.ts} (88%)
delete mode 100644 packages/editor/src/core/props/index.ts
delete mode 100644 packages/editor/src/core/props/read-only.tsx
diff --git a/apps/space/core/components/editor/index.ts b/apps/space/core/components/editor/index.ts
index de164c8376d..69e2006e346 100644
--- a/apps/space/core/components/editor/index.ts
+++ b/apps/space/core/components/editor/index.ts
@@ -1,5 +1,4 @@
export * from "./embeds";
export * from "./lite-text-editor";
-export * from "./lite-text-read-only-editor";
export * from "./rich-text-editor";
export * from "./toolbar";
diff --git a/apps/space/core/components/editor/lite-text-editor.tsx b/apps/space/core/components/editor/lite-text-editor.tsx
index 6342d934479..9a29dac9d10 100644
--- a/apps/space/core/components/editor/lite-text-editor.tsx
+++ b/apps/space/core/components/editor/lite-text-editor.tsx
@@ -1,6 +1,6 @@
import React from "react";
// plane imports
-import { EditorRefApi, ILiteTextEditorProps, LiteTextEditorWithRef, TFileHandler } from "@plane/editor";
+import { type EditorRefApi, type ILiteTextEditorProps, LiteTextEditorWithRef, type TFileHandler } from "@plane/editor";
import { MakeOptional } from "@plane/types";
import { cn } from "@plane/utils";
// components
@@ -9,28 +9,34 @@ import { EditorMentionsRoot, IssueCommentToolbar } from "@/components/editor";
import { getEditorFileHandlers } from "@/helpers/editor.helper";
import { isCommentEmpty } from "@/helpers/string.helper";
-interface LiteTextEditorWrapperProps
- extends MakeOptional<
- Omit,
- "disabledExtensions" | "flaggedExtensions"
- > {
+type LiteTextEditorWrapperProps = MakeOptional<
+ Omit,
+ "disabledExtensions" | "flaggedExtensions"
+> & {
anchor: string;
- workspaceId: string;
isSubmitting?: boolean;
showSubmitButton?: boolean;
- uploadFile: TFileHandler["upload"];
-}
+ workspaceId: string;
+} & (
+ | {
+ editable: false;
+ }
+ | {
+ editable: true;
+ uploadFile: TFileHandler["upload"];
+ }
+ );
export const LiteTextEditor = React.forwardRef((props, ref) => {
const {
anchor,
containerClassName,
- workspaceId,
- isSubmitting = false,
- showSubmitButton = true,
- uploadFile,
disabledExtensions,
+ editable,
flaggedExtensions,
+ isSubmitting = false,
+ showSubmitButton = true,
+ workspaceId,
...rest
} = props;
function isMutableRefObject(ref: React.ForwardedRef): ref is React.MutableRefObject {
@@ -46,9 +52,10 @@ export const LiteTextEditor = React.forwardRef "",
workspaceId,
})}
mentionHandler={{
diff --git a/apps/space/core/components/editor/lite-text-read-only-editor.tsx b/apps/space/core/components/editor/lite-text-read-only-editor.tsx
deleted file mode 100644
index 34662b7fc37..00000000000
--- a/apps/space/core/components/editor/lite-text-read-only-editor.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from "react";
-// plane imports
-import { EditorReadOnlyRefApi, ILiteTextReadOnlyEditorProps, LiteTextReadOnlyEditorWithRef } 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 LiteTextReadOnlyEditorWrapperProps = MakeOptional<
- Omit,
- "disabledExtensions" | "flaggedExtensions"
-> & {
- anchor: string;
- workspaceId: string;
-};
-
-export const LiteTextReadOnlyEditor = 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(props.containerClassName, "relative p-2")}
- />
- );
- }
-);
-
-LiteTextReadOnlyEditor.displayName = "LiteTextReadOnlyEditor";
diff --git a/apps/space/core/components/issues/peek-overview/comment/add-comment.tsx b/apps/space/core/components/issues/peek-overview/comment/add-comment.tsx
index d746d776622..b68a7ab96e0 100644
--- a/apps/space/core/components/issues/peek-overview/comment/add-comment.tsx
+++ b/apps/space/core/components/issues/peek-overview/comment/add-comment.tsx
@@ -75,6 +75,7 @@ export const AddComment: React.FC = observer((props) => {
control={control}
render={({ field: { value, onChange } }) => (
{
if (currentUser) handleSubmit(onSubmit)(e);
}}
diff --git a/apps/space/core/components/issues/peek-overview/comment/comment-detail-card.tsx b/apps/space/core/components/issues/peek-overview/comment/comment-detail-card.tsx
index 9adebeb2db4..db7c79454a5 100644
--- a/apps/space/core/components/issues/peek-overview/comment/comment-detail-card.tsx
+++ b/apps/space/core/components/issues/peek-overview/comment/comment-detail-card.tsx
@@ -8,7 +8,7 @@ import { EditorRefApi } from "@plane/editor";
import { TIssuePublicComment } from "@plane/types";
import { getFileURL } from "@plane/utils";
// components
-import { LiteTextEditor, LiteTextReadOnlyEditor } from "@/components/editor";
+import { LiteTextEditor } from "@/components/editor";
import { CommentReactions } from "@/components/issues/peek-overview";
// helpers
import { timeAgo } from "@/helpers/date-time.helper";
@@ -102,6 +102,7 @@ export const CommentCard: React.FC = observer((props) => {
name="comment_html"
render={({ field: { onChange, value } }) => (
= observer((props) => {
-
;
+ readOnlyEditorRef: React.RefObject;
showAccessSpecifier: boolean;
workspaceId: string;
workspaceSlug: string;
@@ -67,7 +67,8 @@ export const CommentCardDisplay: React.FC = observer((props) => {
)}
)}
-
"}
diff --git a/apps/web/core/components/editor/lite-text/editor.tsx b/apps/web/core/components/editor/lite-text/editor.tsx
index 088676358ee..d1e2017905e 100644
--- a/apps/web/core/components/editor/lite-text/editor.tsx
+++ b/apps/web/core/components/editor/lite-text/editor.tsx
@@ -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,
- "disabledExtensions" | "flaggedExtensions"
- > {
+type LiteTextEditorWrapperProps = MakeOptional<
+ Omit,
+ "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((props, ref) => {
const { t } = useTranslation();
const {
containerClassName,
+ editable,
workspaceSlug,
workspaceId,
projectId,
@@ -57,7 +60,6 @@ export const LiteTextEditor = React.forwardRef
- 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 "",
workspaceId,
workspaceSlug,
})}
@@ -107,8 +110,10 @@ export const LiteTextEditor = React.forwardRef ,
- 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")}
diff --git a/apps/web/core/components/editor/lite-text/index.ts b/apps/web/core/components/editor/lite-text/index.ts
index a53feeb8277..f73ee92ef6e 100644
--- a/apps/web/core/components/editor/lite-text/index.ts
+++ b/apps/web/core/components/editor/lite-text/index.ts
@@ -1,3 +1,2 @@
export * from "./editor";
-export * from "./read-only-editor";
export * from "./toolbar";
diff --git a/apps/web/core/components/editor/lite-text/read-only-editor.tsx b/apps/web/core/components/editor/lite-text/read-only-editor.tsx
deleted file mode 100644
index e37b0c30ee7..00000000000
--- a/apps/web/core/components/editor/lite-text/read-only-editor.tsx
+++ /dev/null
@@ -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,
- "disabledExtensions" | "flaggedExtensions"
-> & {
- workspaceId: string;
- workspaceSlug: string;
- projectId?: string;
-};
-
-export const LiteTextReadOnlyEditor = React.forwardRef(
- ({ 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 (
- ,
- 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";
diff --git a/apps/web/core/components/editor/rich-text/editor.tsx b/apps/web/core/components/editor/rich-text/editor.tsx
index 7349bfe48db..735d81e2313 100644
--- a/apps/web/core/components/editor/rich-text/editor.tsx
+++ b/apps/web/core/components/editor/rich-text/editor.tsx
@@ -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 ,
- 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)}
diff --git a/apps/web/core/components/editor/sticky-editor/editor.tsx b/apps/web/core/components/editor/sticky-editor/editor.tsx
index f91cdd498f9..81149e41bf4 100644
--- a/apps/web/core/components/editor/sticky-editor/editor.tsx
+++ b/apps/web/core/components/editor/sticky-editor/editor.tsx
@@ -14,7 +14,10 @@ import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
import { StickyEditorToolbar } from "./toolbar";
interface StickyEditorWrapperProps
- extends Omit {
+ extends Omit<
+ ILiteTextEditorProps,
+ "disabledExtensions" | "editable" | "flaggedExtensions" | "fileHandler" | "mentionHandler"
+ > {
workspaceSlug: string;
workspaceId: string;
projectId?: string;
@@ -67,6 +70,7 @@ export const StickyEditor = React.forwardRef {
// file size
const { maxFileSize } = useFileSize();
- const getReadOnlyEditorFileHandlers = useCallback(
- (args: Pick): 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,
};
};
diff --git a/packages/editor/src/ce/extensions/core/index.ts b/packages/editor/src/ce/extensions/core/index.ts
index 9ffc978c3f0..e121e50b065 100644
--- a/packages/editor/src/ce/extensions/core/index.ts
+++ b/packages/editor/src/ce/extensions/core/index.ts
@@ -1,2 +1 @@
export * from "./extensions";
-export * from "./read-only-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
deleted file mode 100644
index 4f9306da302..00000000000
--- a/packages/editor/src/ce/extensions/core/read-only-extensions.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Extensions } from "@tiptap/core";
-// types
-import type { IReadOnlyEditorProps } from "@/types";
-
-export type TCoreReadOnlyEditorAdditionalExtensionsProps = Pick<
- IReadOnlyEditorProps,
- "disabledExtensions" | "flaggedExtensions"
->;
-
-export const CoreReadOnlyEditorAdditionalExtensions = (
- props: TCoreReadOnlyEditorAdditionalExtensionsProps
-): Extensions => {
- const {} = props;
- return [];
-};
diff --git a/packages/editor/src/ce/extensions/rich-text/extensions.tsx b/packages/editor/src/ce/extensions/rich-text-extensions.tsx
similarity index 100%
rename from packages/editor/src/ce/extensions/rich-text/extensions.tsx
rename to packages/editor/src/ce/extensions/rich-text-extensions.tsx
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/index.ts b/packages/editor/src/core/components/editors/index.ts
index 03ada4f7261..6248f407d95 100644
--- a/packages/editor/src/core/components/editors/index.ts
+++ b/packages/editor/src/core/components/editors/index.ts
@@ -4,4 +4,3 @@ export * from "./rich-text";
export * from "./editor-container";
export * from "./editor-content";
export * from "./editor-wrapper";
-export * from "./read-only-editor-wrapper";
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 66913057b8b..df89521ae68 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/lite-text/index.ts b/packages/editor/src/core/components/editors/lite-text/index.ts
index b2ba8682a3c..8b1fd904bb0 100644
--- a/packages/editor/src/core/components/editors/lite-text/index.ts
+++ b/packages/editor/src/core/components/editors/lite-text/index.ts
@@ -1,2 +1 @@
export * from "./editor";
-export * from "./read-only-editor";
diff --git a/packages/editor/src/core/components/editors/lite-text/read-only-editor.tsx b/packages/editor/src/core/components/editors/lite-text/read-only-editor.tsx
deleted file mode 100644
index 75e02791de2..00000000000
--- a/packages/editor/src/core/components/editors/lite-text/read-only-editor.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { forwardRef } from "react";
-// components
-import { ReadOnlyEditorWrapper } from "@/components/editors";
-// types
-import { EditorReadOnlyRefApi, ILiteTextReadOnlyEditorProps } from "@/types";
-
-const LiteTextReadOnlyEditorWithRef = forwardRef((props, ref) => (
- } />
-));
-
-LiteTextReadOnlyEditorWithRef.displayName = "LiteReadOnlyEditorWithRef";
-
-export { LiteTextReadOnlyEditorWithRef };
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
deleted file mode 100644
index b6abd1a6a50..00000000000
--- a/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-// components
-import { EditorContainer, EditorContentWrapper } from "@/components/editors";
-// constants
-import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config";
-// helpers
-import { getEditorClassNames } from "@/helpers/common";
-// hooks
-import { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
-// types
-import { IReadOnlyEditorProps } from "@/types";
-
-export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
- const {
- containerClassName,
- disabledExtensions,
- displayConfig = DEFAULT_DISPLAY_CONFIG,
- editorClassName = "",
- extensions,
- fileHandler,
- flaggedExtensions,
- forwardedRef,
- id,
- initialValue,
- mentionHandler,
- } = props;
-
- const editor = useReadOnlyEditor({
- disabledExtensions,
- editorClassName,
- extensions,
- fileHandler,
- flaggedExtensions,
- forwardedRef,
- initialValue,
- mentionHandler,
- });
-
- const editorContainerClassName = getEditorClassNames({
- containerClassName,
- });
-
- if (!editor) return null;
-
- return (
-
-
-
-
-
- );
-};
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..02c0db55d6c 100644
--- a/packages/editor/src/core/components/editors/rich-text/editor.tsx
+++ b/packages/editor/src/core/components/editors/rich-text/editor.tsx
@@ -5,7 +5,7 @@ import { EditorBubbleMenu } from "@/components/menus";
// extensions
import { SideMenuExtension } from "@/extensions";
// plane editor imports
-import { RichTextEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/extensions";
+import { RichTextEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text-extensions";
// types
import { EditorRefApi, IRichTextEditorProps } from "@/types";
diff --git a/packages/editor/src/core/extensions/callout/extension.tsx b/packages/editor/src/core/extensions/callout/extension.tsx
index 0629a0f7704..9bc26df7240 100644
--- a/packages/editor/src/core/extensions/callout/extension.tsx
+++ b/packages/editor/src/core/extensions/callout/extension.tsx
@@ -1,6 +1,6 @@
import { findParentNodeClosestToPos, Predicate, ReactNodeViewRenderer } from "@tiptap/react";
// extensions
-import { CustomCalloutBlock, CustomCalloutNodeViewProps } from "@/extensions/callout";
+import { CustomCalloutBlock, CustomCalloutNodeViewProps } from "@/extensions/callout/block";
// helpers
import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary";
// config
diff --git a/packages/editor/src/core/extensions/callout/index.ts b/packages/editor/src/core/extensions/callout/index.ts
index 57915d313f6..2ce32da8ba5 100644
--- a/packages/editor/src/core/extensions/callout/index.ts
+++ b/packages/editor/src/core/extensions/callout/index.ts
@@ -1,3 +1 @@
-export * from "./block";
export * from "./extension";
-export * from "./read-only-extension";
diff --git a/packages/editor/src/core/extensions/callout/read-only-extension.tsx b/packages/editor/src/core/extensions/callout/read-only-extension.tsx
deleted file mode 100644
index c1279ff8807..00000000000
--- a/packages/editor/src/core/extensions/callout/read-only-extension.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { ReactNodeViewRenderer } from "@tiptap/react";
-// extensions
-import { CustomCalloutBlock, CustomCalloutNodeViewProps } from "@/extensions/callout";
-// config
-import { CustomCalloutExtensionConfig } from "./extension-config";
-
-export const CustomCalloutReadOnlyExtension = CustomCalloutExtensionConfig.extend({
- selectable: false,
- draggable: false,
-
- addNodeView() {
- return ReactNodeViewRenderer((props) => (
-
- ));
- },
-});
diff --git a/packages/editor/src/core/extensions/custom-image/extension.tsx b/packages/editor/src/core/extensions/custom-image/extension.tsx
index da82c6a6f12..8aefd80d03d 100644
--- a/packages/editor/src/core/extensions/custom-image/extension.tsx
+++ b/packages/editor/src/core/extensions/custom-image/extension.tsx
@@ -6,14 +6,14 @@ import { ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config";
import { isFileValid } from "@/helpers/file";
import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary";
// types
-import type { TFileHandler, TReadOnlyFileHandler } from "@/types";
+import type { TFileHandler } from "@/types";
// local imports
import { CustomImageNodeView, CustomImageNodeViewProps } from "./components/node-view";
import { CustomImageExtensionConfig } from "./extension-config";
import { getImageComponentImageFileMap } from "./utils";
type Props = {
- fileHandler: TFileHandler | TReadOnlyFileHandler;
+ fileHandler: TFileHandler;
isEditable: boolean;
};
diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx
index 3cee3b10f03..0a61fd244fb 100644
--- a/packages/editor/src/core/extensions/image/extension.tsx
+++ b/packages/editor/src/core/extensions/image/extension.tsx
@@ -2,7 +2,7 @@ import { ReactNodeViewRenderer } from "@tiptap/react";
// helpers
import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary";
// types
-import type { TFileHandler, TReadOnlyFileHandler } from "@/types";
+import type { TFileHandler } from "@/types";
// local imports
import { CustomImageNodeView, CustomImageNodeViewProps } from "../custom-image/components/node-view";
import { ImageExtensionConfig } from "./extension-config";
@@ -12,7 +12,7 @@ export type ImageExtensionStorage = {
};
type Props = {
- fileHandler: TFileHandler | TReadOnlyFileHandler;
+ fileHandler: TFileHandler;
};
export const ImageExtension = (props: Props) => {
diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts
index c3a8e5d5c7a..48692c09181 100644
--- a/packages/editor/src/core/extensions/index.ts
+++ b/packages/editor/src/core/extensions/index.ts
@@ -17,7 +17,6 @@ export * from "./headings-list";
export * from "./horizontal-rule";
export * from "./keymap";
export * from "./quote";
-export * from "./read-only-extensions";
export * from "./side-menu";
export * from "./text-align";
export * from "./utility";
diff --git a/packages/editor/src/core/extensions/read-only-extensions.ts b/packages/editor/src/core/extensions/read-only-extensions.ts
deleted file mode 100644
index 3099fa88559..00000000000
--- a/packages/editor/src/core/extensions/read-only-extensions.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { Extensions } from "@tiptap/core";
-import CharacterCount from "@tiptap/extension-character-count";
-import TaskItem from "@tiptap/extension-task-item";
-import TaskList from "@tiptap/extension-task-list";
-import TextStyle from "@tiptap/extension-text-style";
-import TiptapUnderline from "@tiptap/extension-underline";
-import { Markdown } from "tiptap-markdown";
-// extensions
-import {
- CustomQuoteExtension,
- CustomHorizontalRule,
- CustomLinkExtension,
- CustomTypographyExtension,
- CustomCodeBlockExtension,
- CustomCodeInlineExtension,
- TableHeader,
- TableCell,
- TableRow,
- Table,
- CustomMentionExtension,
- CustomTextAlignExtension,
- CustomCalloutReadOnlyExtension,
- CustomColorExtension,
- UtilityExtension,
- ImageExtension,
-} from "@/extensions";
-// plane editor extensions
-import { CoreReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions";
-// types
-import type { IReadOnlyEditorProps } from "@/types";
-// local imports
-import { CustomImageExtension } from "./custom-image/extension";
-import { EmojiExtension } from "./emoji/extension";
-import { CustomStarterKitExtension } from "./starter-kit";
-
-type Props = Pick;
-
-export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
- const { disabledExtensions, fileHandler, flaggedExtensions, mentionHandler } = props;
-
- const extensions = [
- CustomStarterKitExtension({
- enableHistory: false,
- }),
- EmojiExtension,
- CustomQuoteExtension,
- CustomHorizontalRule,
- CustomLinkExtension,
- CustomTypographyExtension,
- TiptapUnderline,
- TextStyle,
- TaskList.configure({
- HTMLAttributes: {
- class: "not-prose pl-2 space-y-2",
- },
- }),
- TaskItem.configure({
- HTMLAttributes: {
- class: "relative pointer-events-none",
- },
- nested: true,
- }),
- CustomCodeBlockExtension,
- CustomCodeInlineExtension,
- Markdown.configure({
- html: true,
- transformCopiedText: false,
- }),
- Table,
- TableHeader,
- TableCell,
- TableRow,
- CustomMentionExtension(mentionHandler),
- CharacterCount,
- CustomColorExtension,
- CustomTextAlignExtension,
- CustomCalloutReadOnlyExtension,
- UtilityExtension({
- disabledExtensions,
- fileHandler,
- isEditable: false,
- }),
- ...CoreReadOnlyEditorAdditionalExtensions({
- disabledExtensions,
- flaggedExtensions,
- }),
- ];
-
- if (!disabledExtensions.includes("image")) {
- extensions.push(
- ImageExtension({
- fileHandler,
- }),
- CustomImageExtension({
- fileHandler,
- isEditable: false,
- })
- );
- }
-
- return extensions;
-};
diff --git a/packages/editor/src/core/extensions/utility.ts b/packages/editor/src/core/extensions/utility.ts
index a1b138a6d26..ba9e031b027 100644
--- a/packages/editor/src/core/extensions/utility.ts
+++ b/packages/editor/src/core/extensions/utility.ts
@@ -10,7 +10,7 @@ import { FilePlugins } from "@/plugins/file/root";
import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard";
// types
-import type { IEditorProps, TEditorAsset, TFileHandler, TReadOnlyFileHandler } from "@/types";
+import type { IEditorProps, TEditorAsset, TFileHandler } from "@/types";
type TActiveDropbarExtensions = CORE_EXTENSIONS.MENTION | CORE_EXTENSIONS.EMOJI | TAdditionalActiveDropbarExtensions;
declare module "@tiptap/core" {
@@ -38,7 +38,7 @@ export interface UtilityExtensionStorage {
}
type Props = Pick & {
- fileHandler: TFileHandler | TReadOnlyFileHandler;
+ fileHandler: TFileHandler;
isEditable: boolean;
};
diff --git a/packages/editor/src/core/helpers/editor-ref.ts b/packages/editor/src/core/helpers/editor-ref.ts
index 1b9843df975..520e840bf59 100644
--- a/packages/editor/src/core/helpers/editor-ref.ts
+++ b/packages/editor/src/core/helpers/editor-ref.ts
@@ -1,22 +1,26 @@
import { HocuspocusProvider } from "@hocuspocus/provider";
import { Editor } from "@tiptap/core";
+import { DOMSerializer } from "@tiptap/pm/model";
import * as Y from "yjs";
+// components
+import { getEditorMenuItems } from "@/components/menus";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
import { CORE_EDITOR_META } from "@/constants/meta";
// types
-import { EditorReadOnlyRefApi } from "@/types";
+import type { EditorRefApi, TEditorCommands } from "@/types";
// local imports
import { getParagraphCount } from "./common";
import { getExtensionStorage } from "./get-extension-storage";
-import { scrollSummary } from "./scroll-to-node";
+import { insertContentAtSavedSelection } from "./insert-content-at-cursor-position";
+import { scrollSummary, scrollToNodeViaDOMCoordinates } from "./scroll-to-node";
type TArgs = {
editor: Editor | null;
provider: HocuspocusProvider | undefined;
};
-export const getEditorRefHelpers = (args: TArgs): EditorReadOnlyRefApi => {
+export const getEditorRefHelpers = (args: TArgs): EditorRefApi => {
const { editor, provider } = args;
return {
@@ -51,5 +55,147 @@ export const getEditorRefHelpers = (args: TArgs): EditorReadOnlyRefApi => {
setEditorValue: (content, emitUpdate = false) => {
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: true });
},
+ blur: () => editor?.commands.blur(),
+ emitRealTimeUpdate: (message) => provider?.sendStateless(message),
+ executeMenuItemCommand: (props) => {
+ const { itemKey } = props;
+ const editorItems = getEditorMenuItems(editor);
+
+ const getEditorMenuItem = (itemKey: TEditorCommands) => editorItems.find((item) => item.key === itemKey);
+
+ const item = getEditorMenuItem(itemKey);
+ if (item) {
+ item.command(props);
+ } else {
+ console.warn(`No command found for item: ${itemKey}`);
+ }
+ },
+ getCurrentCursorPosition: () => editor?.state.selection.from,
+ getSelectedText: () => {
+ if (!editor) return null;
+
+ const { state } = editor;
+ const { from, to, empty } = state.selection;
+
+ if (empty) return null;
+
+ const nodesArray: string[] = [];
+ state.doc.nodesBetween(from, to, (node, _pos, parent) => {
+ if (parent === state.doc && editor) {
+ const serializer = DOMSerializer.fromSchema(editor.schema);
+ const dom = serializer.serializeNode(node);
+ const tempDiv = document.createElement("div");
+ tempDiv.appendChild(dom);
+ nodesArray.push(tempDiv.innerHTML);
+ }
+ });
+ const selection = nodesArray.join("");
+ return selection;
+ },
+ insertText: (contentHTML, insertOnNextLine) => {
+ if (!editor) return;
+ const { from, to, empty } = editor.state.selection;
+ if (empty) return;
+ if (insertOnNextLine) {
+ // move cursor to the end of the selection and insert a new line
+ editor.chain().focus().setTextSelection(to).insertContent("
").insertContent(contentHTML).run();
+ } else {
+ // replace selected text with the content provided
+ editor.chain().focus().deleteRange({ from, to }).insertContent(contentHTML).run();
+ }
+ },
+ isEditorReadyToDiscard: () =>
+ !!editor && getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress === false,
+ isMenuItemActive: (props) => {
+ const { itemKey } = props;
+ const editorItems = getEditorMenuItems(editor);
+
+ const getEditorMenuItem = (itemKey: TEditorCommands) => editorItems.find((item) => item.key === itemKey);
+ const item = getEditorMenuItem(itemKey);
+ if (!item) return false;
+
+ return item.isActive(props);
+ },
+ listenToRealTimeUpdate: () => provider && { on: provider.on.bind(provider), off: provider.off.bind(provider) },
+ onDocumentInfoChange: (callback) => {
+ const handleDocumentInfoChange = () => {
+ if (!editor) return;
+ callback({
+ characters: editor ? getExtensionStorage(editor, CORE_EXTENSIONS.CHARACTER_COUNT)?.characters?.() : 0,
+ paragraphs: getParagraphCount(editor?.state),
+ words: editor ? getExtensionStorage(editor, CORE_EXTENSIONS.CHARACTER_COUNT)?.words?.() : 0,
+ });
+ };
+
+ // Subscribe to update event emitted from character count extension
+ editor?.on("update", handleDocumentInfoChange);
+ // Return a function to unsubscribe to the continuous transactions of
+ // the editor on unmounting the component that has subscribed to this
+ // method
+ return () => {
+ editor?.off("update", handleDocumentInfoChange);
+ };
+ },
+ onHeadingChange: (callback) => {
+ const handleHeadingChange = () => {
+ if (!editor) return;
+ const headings = getExtensionStorage(editor, CORE_EXTENSIONS.HEADINGS_LIST)?.headings;
+ if (headings) {
+ callback(headings);
+ }
+ };
+
+ // Subscribe to update event emitted from headers extension
+ editor?.on("update", handleHeadingChange);
+ // Return a function to unsubscribe to the continuous transactions of
+ // the editor on unmounting the component that has subscribed to this
+ // method
+ return () => {
+ editor?.off("update", handleHeadingChange);
+ };
+ },
+ onStateChange: (callback) => {
+ // Subscribe to editor state changes
+ editor?.on("transaction", callback);
+
+ // Return a function to unsubscribe to the continuous transactions of
+ // the editor on unmounting the component that has subscribed to this
+ // method
+ return () => {
+ editor?.off("transaction", callback);
+ };
+ },
+ scrollToNodeViaDOMCoordinates(behavior, pos) {
+ const resolvedPos = pos ?? editor?.state.selection.from;
+ if (!editor || !resolvedPos) return;
+ scrollToNodeViaDOMCoordinates(editor, resolvedPos, behavior);
+ },
+ setEditorValueAtCursorPosition: (content) => {
+ if (editor?.state.selection) {
+ insertContentAtSavedSelection(editor, content);
+ }
+ },
+ setFocusAtPosition: (position) => {
+ if (!editor || editor.isDestroyed) {
+ console.error("Editor reference is not available or has been destroyed.");
+ return;
+ }
+ try {
+ const docSize = editor.state.doc.content.size;
+ const safePosition = Math.max(0, Math.min(position, docSize));
+ editor
+ .chain()
+ .insertContentAt(safePosition, [{ type: CORE_EXTENSIONS.PARAGRAPH }])
+ .focus()
+ .run();
+ } catch (error) {
+ console.error("An error occurred while setting focus at position:", error);
+ }
+ },
+ setProviderDocument: (value) => {
+ const document = provider?.document;
+ if (!document) return;
+ Y.applyUpdate(document, value);
+ },
};
};
diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts
index 954ef61b84d..96f5ba24d98 100644
--- a/packages/editor/src/core/hooks/use-editor.ts
+++ b/packages/editor/src/core/hooks/use-editor.ts
@@ -1,23 +1,16 @@
-import { DOMSerializer } from "@tiptap/pm/model";
import { useEditorState, useEditor as useTiptapEditor } from "@tiptap/react";
import { useImperativeHandle, useEffect } from "react";
-import * as Y from "yjs";
-// components
-import { getEditorMenuItems } from "@/components/menus";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
// extensions
import { CoreEditorExtensions } from "@/extensions";
// helpers
-import { getParagraphCount } from "@/helpers/common";
import { getEditorRefHelpers } from "@/helpers/editor-ref";
import { getExtensionStorage } from "@/helpers/get-extension-storage";
-import { insertContentAtSavedSelection } from "@/helpers/insert-content-at-cursor-position";
-import { scrollToNodeViaDOMCoordinates } from "@/helpers/scroll-to-node";
// props
import { CoreEditorProps } from "@/props";
// types
-import type { TEditorCommands, TEditorHookProps } from "@/types";
+import type { TEditorHookProps } from "@/types";
export const useEditor = (props: TEditorHookProps) => {
const {
@@ -124,155 +117,7 @@ export const useEditor = (props: TEditorHookProps) => {
onAssetChange(assets);
}, [assetsList?.assets, onAssetChange]);
- useImperativeHandle(
- forwardedRef,
- () => ({
- ...getEditorRefHelpers({ editor, provider }),
- blur: () => editor?.commands.blur(),
- emitRealTimeUpdate: (message) => provider?.sendStateless(message),
- executeMenuItemCommand: (props) => {
- const { itemKey } = props;
- const editorItems = getEditorMenuItems(editor);
-
- const getEditorMenuItem = (itemKey: TEditorCommands) => editorItems.find((item) => item.key === itemKey);
-
- const item = getEditorMenuItem(itemKey);
- if (item) {
- item.command(props);
- } else {
- console.warn(`No command found for item: ${itemKey}`);
- }
- },
- getCurrentCursorPosition: () => editor?.state.selection.from,
- getSelectedText: () => {
- if (!editor) return null;
-
- const { state } = editor;
- const { from, to, empty } = state.selection;
-
- if (empty) return null;
-
- const nodesArray: string[] = [];
- state.doc.nodesBetween(from, to, (node, _pos, parent) => {
- if (parent === state.doc && editor) {
- const serializer = DOMSerializer.fromSchema(editor.schema);
- const dom = serializer.serializeNode(node);
- const tempDiv = document.createElement("div");
- tempDiv.appendChild(dom);
- nodesArray.push(tempDiv.innerHTML);
- }
- });
- const selection = nodesArray.join("");
- return selection;
- },
- insertText: (contentHTML, insertOnNextLine) => {
- if (!editor) return;
- const { from, to, empty } = editor.state.selection;
- if (empty) return;
- if (insertOnNextLine) {
- // move cursor to the end of the selection and insert a new line
- editor.chain().focus().setTextSelection(to).insertContent("
").insertContent(contentHTML).run();
- } else {
- // replace selected text with the content provided
- editor.chain().focus().deleteRange({ from, to }).insertContent(contentHTML).run();
- }
- },
- isEditorReadyToDiscard: () =>
- !!editor && getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress === false,
- isMenuItemActive: (props) => {
- const { itemKey } = props;
- const editorItems = getEditorMenuItems(editor);
-
- const getEditorMenuItem = (itemKey: TEditorCommands) => editorItems.find((item) => item.key === itemKey);
- const item = getEditorMenuItem(itemKey);
- if (!item) return false;
-
- return item.isActive(props);
- },
- listenToRealTimeUpdate: () => provider && { on: provider.on.bind(provider), off: provider.off.bind(provider) },
- onDocumentInfoChange: (callback) => {
- const handleDocumentInfoChange = () => {
- if (!editor) return;
- callback({
- characters: editor ? getExtensionStorage(editor, CORE_EXTENSIONS.CHARACTER_COUNT)?.characters?.() : 0,
- paragraphs: getParagraphCount(editor?.state),
- words: editor ? getExtensionStorage(editor, CORE_EXTENSIONS.CHARACTER_COUNT)?.words?.() : 0,
- });
- };
-
- // Subscribe to update event emitted from character count extension
- editor?.on("update", handleDocumentInfoChange);
- // Return a function to unsubscribe to the continuous transactions of
- // the editor on unmounting the component that has subscribed to this
- // method
- return () => {
- editor?.off("update", handleDocumentInfoChange);
- };
- },
- onHeadingChange: (callback) => {
- const handleHeadingChange = () => {
- if (!editor) return;
- const headings = getExtensionStorage(editor, CORE_EXTENSIONS.HEADINGS_LIST)?.headings;
- if (headings) {
- callback(headings);
- }
- };
-
- // Subscribe to update event emitted from headers extension
- editor?.on("update", handleHeadingChange);
- // Return a function to unsubscribe to the continuous transactions of
- // the editor on unmounting the component that has subscribed to this
- // method
- return () => {
- editor?.off("update", handleHeadingChange);
- };
- },
- onStateChange: (callback) => {
- // Subscribe to editor state changes
- editor?.on("transaction", callback);
-
- // Return a function to unsubscribe to the continuous transactions of
- // the editor on unmounting the component that has subscribed to this
- // method
- return () => {
- editor?.off("transaction", callback);
- };
- },
- scrollToNodeViaDOMCoordinates(behavior, pos) {
- const resolvedPos = pos ?? editor?.state.selection.from;
- if (!editor || !resolvedPos) return;
- scrollToNodeViaDOMCoordinates(editor, resolvedPos, behavior);
- },
- setEditorValueAtCursorPosition: (content) => {
- if (editor?.state.selection) {
- insertContentAtSavedSelection(editor, content);
- }
- },
- setFocusAtPosition: (position) => {
- if (!editor || editor.isDestroyed) {
- console.error("Editor reference is not available or has been destroyed.");
- return;
- }
- try {
- const docSize = editor.state.doc.content.size;
- const safePosition = Math.max(0, Math.min(position, docSize));
- editor
- .chain()
- .insertContentAt(safePosition, [{ type: CORE_EXTENSIONS.PARAGRAPH }])
- .focus()
- .run();
- } catch (error) {
- console.error("An error occurred while setting focus at position:", error);
- }
- },
- setProviderDocument: (value) => {
- const document = provider?.document;
- if (!document) return;
- Y.applyUpdate(document, value);
- },
- }),
- [editor, provider]
- );
+ useImperativeHandle(forwardedRef, () => getEditorRefHelpers({ editor, provider }), [editor, provider]);
if (!editor) {
return null;
diff --git a/packages/editor/src/core/hooks/use-read-only-editor.ts b/packages/editor/src/core/hooks/use-read-only-editor.ts
deleted file mode 100644
index 43e9c958134..00000000000
--- a/packages/editor/src/core/hooks/use-read-only-editor.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { useEditor as useTiptapEditor } from "@tiptap/react";
-import { useImperativeHandle, useEffect } from "react";
-// extensions
-import { CoreReadOnlyEditorExtensions } from "@/extensions";
-// helpers
-import { getEditorRefHelpers } from "@/helpers/editor-ref";
-// props
-import { CoreReadOnlyEditorProps } from "@/props";
-// types
-import type { TReadOnlyEditorHookProps } from "@/types";
-
-export const useReadOnlyEditor = (props: TReadOnlyEditorHookProps) => {
- const {
- disabledExtensions,
- editorClassName = "",
- editorProps = {},
- extensions = [],
- fileHandler,
- flaggedExtensions,
- forwardedRef,
- handleEditorReady,
- initialValue,
- mentionHandler,
- provider,
- } = props;
-
- const editor = useTiptapEditor({
- editable: false,
- immediatelyRender: false,
- shouldRerenderOnTransaction: false,
- content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "",
- parseOptions: { preserveWhitespace: true },
- editorProps: {
- ...CoreReadOnlyEditorProps({
- editorClassName,
- }),
- ...editorProps,
- },
- onCreate: async () => {
- handleEditorReady?.(true);
- },
- extensions: [
- ...CoreReadOnlyEditorExtensions({
- disabledExtensions,
- fileHandler,
- flaggedExtensions,
- mentionHandler,
- }),
- ...extensions,
- ],
- onDestroy: () => {
- handleEditorReady?.(false);
- },
- });
-
- // for syncing swr data on tab refocus etc
- useEffect(() => {
- if (initialValue === null || initialValue === undefined) return;
- if (editor && !editor.isDestroyed) editor?.commands.setContent(initialValue, false, { preserveWhitespace: true });
- }, [editor, initialValue]);
-
- useImperativeHandle(forwardedRef, () => getEditorRefHelpers({ editor, provider }));
-
- if (!editor) {
- return null;
- }
-
- return editor;
-};
diff --git a/packages/editor/src/core/plugins/file/root.ts b/packages/editor/src/core/plugins/file/root.ts
index 693ac6964ba..bf9ca0260e7 100644
--- a/packages/editor/src/core/plugins/file/root.ts
+++ b/packages/editor/src/core/plugins/file/root.ts
@@ -1,14 +1,14 @@
import { Editor } from "@tiptap/core";
import { Plugin } from "@tiptap/pm/state";
// types
-import { TFileHandler, TReadOnlyFileHandler } from "@/types";
+import { TFileHandler } from "@/types";
// local imports
import { TrackFileDeletionPlugin } from "./delete";
import { TrackFileRestorationPlugin } from "./restore";
type TArgs = {
editor: Editor;
- fileHandler: TFileHandler | TReadOnlyFileHandler;
+ fileHandler: TFileHandler;
isEditable: boolean;
};
diff --git a/packages/editor/src/core/props/props.tsx b/packages/editor/src/core/props.ts
similarity index 88%
rename from packages/editor/src/core/props/props.tsx
rename to packages/editor/src/core/props.ts
index ee0b9e50007..939ca77176d 100644
--- a/packages/editor/src/core/props/props.tsx
+++ b/packages/editor/src/core/props.ts
@@ -2,11 +2,11 @@ import { EditorProps } from "@tiptap/pm/view";
// plane utils
import { cn } from "@plane/utils";
-export type TCoreEditorProps = {
+type TArgs = {
editorClassName: string;
};
-export const CoreEditorProps = (props: TCoreEditorProps): EditorProps => {
+export const CoreEditorProps = (props: TArgs): EditorProps => {
const { editorClassName } = props;
return {
diff --git a/packages/editor/src/core/props/index.ts b/packages/editor/src/core/props/index.ts
deleted file mode 100644
index eaa89f0591d..00000000000
--- a/packages/editor/src/core/props/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./props";
-export * from "./read-only";
diff --git a/packages/editor/src/core/props/read-only.tsx b/packages/editor/src/core/props/read-only.tsx
deleted file mode 100644
index ea5bf09f3a7..00000000000
--- a/packages/editor/src/core/props/read-only.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { EditorProps } from "@tiptap/pm/view";
-// plane utils
-import { cn } from "@plane/utils";
-// props
-import { TCoreEditorProps } from "@/props";
-
-export const CoreReadOnlyEditorProps = (props: TCoreEditorProps): EditorProps => {
- const { editorClassName } = props;
-
- return {
- attributes: {
- class: cn(
- "prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none",
- editorClassName
- ),
- },
- };
-};
diff --git a/packages/editor/src/core/types/config.ts b/packages/editor/src/core/types/config.ts
index 8c1903cf2dc..da00d960769 100644
--- a/packages/editor/src/core/types/config.ts
+++ b/packages/editor/src/core/types/config.ts
@@ -1,17 +1,14 @@
// plane imports
import { TWebhookConnectionQueryParams } from "@plane/types";
-export type TReadOnlyFileHandler = {
+export type TFileHandler = {
+ assetsUploadStatus: Record; // blockId => progress percentage
+ cancel: () => void;
checkIfAssetExists: (assetId: string) => Promise;
+ delete: (assetSrc: string) => Promise;
getAssetDownloadSrc: (path: string) => Promise;
getAssetSrc: (path: string) => Promise;
restore: (assetSrc: string) => Promise;
-};
-
-export type TFileHandler = TReadOnlyFileHandler & {
- assetsUploadStatus: Record; // blockId => progress percentage
- cancel: () => void;
- delete: (assetSrc: string) => Promise;
upload: (blockId: string, file: File) => Promise;
validation: {
/**
diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts
index d511bb7f122..bbf6986067d 100644
--- a/packages/editor/src/core/types/editor.ts
+++ b/packages/editor/src/core/types/editor.ts
@@ -15,8 +15,6 @@ import type {
TExtensions,
TFileHandler,
TMentionHandler,
- TReadOnlyFileHandler,
- TReadOnlyMentionHandler,
TRealtimeConfig,
TServerHandler,
TUserDetails,
@@ -83,9 +81,12 @@ export type TDocumentInfo = {
words: number;
};
-// editor refs
-export type EditorReadOnlyRefApi = {
+export interface EditorRefApi {
+ blur: () => void;
clearEditor: (emitUpdate?: boolean) => void;
+ emitRealTimeUpdate: (action: TDocumentEventsServer) => void;
+ executeMenuItemCommand: (props: TCommandWithPropsWithItemKey) => void;
+ getCurrentCursorPosition: () => number | undefined;
getDocument: () => {
binary: Uint8Array | null;
html: string;
@@ -94,15 +95,6 @@ export type EditorReadOnlyRefApi = {
getDocumentInfo: () => TDocumentInfo;
getHeadings: () => IMarking[];
getMarkDown: () => string;
- scrollSummary: (marking: IMarking) => void;
- setEditorValue: (content: string, emitUpdate?: boolean) => void;
-};
-
-export interface EditorRefApi extends EditorReadOnlyRefApi {
- blur: () => void;
- emitRealTimeUpdate: (action: TDocumentEventsServer) => void;
- executeMenuItemCommand: (props: TCommandWithPropsWithItemKey) => void;
- getCurrentCursorPosition: () => number | undefined;
getSelectedText: () => string | null;
insertText: (contentHTML: string, insertOnNextLine?: boolean) => void;
isEditorReadyToDiscard: () => boolean;
@@ -111,8 +103,10 @@ export interface EditorRefApi extends EditorReadOnlyRefApi {
onDocumentInfoChange: (callback: (documentInfo: TDocumentInfo) => void) => () => void;
onHeadingChange: (callback: (headings: IMarking[]) => void) => () => void;
onStateChange: (callback: () => void) => () => void;
+ scrollSummary: (marking: IMarking) => void;
// eslint-disable-next-line no-undef
scrollToNodeViaDOMCoordinates: (behavior?: ScrollBehavior, position?: number) => void;
+ setEditorValue: (content: string, emitUpdate?: boolean) => void;
setEditorValueAtCursorPosition: (content: string) => void;
setFocusAtPosition: (position: number) => void;
setProviderDocument: (value: Uint8Array) => void;
@@ -125,6 +119,7 @@ export interface IEditorProps {
containerClassName?: string;
displayConfig?: TDisplayConfig;
disabledExtensions: TExtensions[];
+ editable: boolean;
editorClassName?: string;
extensions?: Extensions;
flaggedExtensions: TExtensions[];
@@ -147,13 +142,11 @@ export type ILiteTextEditorProps = IEditorProps;
export type IRichTextEditorProps = IEditorProps & {
dragDropEnabled?: boolean;
- editable: boolean;
};
export interface ICollaborativeDocumentEditorProps
extends Omit {
aiHandler?: TAIHandler;
- editable: boolean;
embedHandler: TEmbedConfig;
realtimeConfig: TRealtimeConfig;
serverHandler?: TServerHandler;
@@ -162,33 +155,11 @@ export interface ICollaborativeDocumentEditorProps
export interface IDocumentEditorProps extends Omit {
aiHandler?: TAIHandler;
- editable: boolean;
embedHandler: TEmbedConfig;
user?: TUserDetails;
value: Content;
}
-// read only editor props
-export interface IReadOnlyEditorProps
- extends Pick<
- IEditorProps,
- | "containerClassName"
- | "disabledExtensions"
- | "flaggedExtensions"
- | "displayConfig"
- | "editorClassName"
- | "extensions"
- | "handleEditorReady"
- | "id"
- | "initialValue"
- > {
- fileHandler: TReadOnlyFileHandler;
- forwardedRef?: React.MutableRefObject;
- mentionHandler: TReadOnlyMentionHandler;
-}
-
-export type ILiteTextReadOnlyEditorProps = IReadOnlyEditorProps;
-
export interface EditorEvents {
beforeCreate: never;
create: never;
diff --git a/packages/editor/src/core/types/hook.ts b/packages/editor/src/core/types/hook.ts
index fa014ceb905..a1dd6ac1dd7 100644
--- a/packages/editor/src/core/types/hook.ts
+++ b/packages/editor/src/core/types/hook.ts
@@ -2,7 +2,7 @@ import type { HocuspocusProvider } from "@hocuspocus/provider";
import type { Content } from "@tiptap/core";
import type { EditorProps } from "@tiptap/pm/view";
// local imports
-import type { ICollaborativeDocumentEditorProps, IEditorProps, IReadOnlyEditorProps } from "./editor";
+import type { ICollaborativeDocumentEditorProps, IEditorProps } from "./editor";
type TCoreHookProps = Pick<
IEditorProps,
@@ -47,7 +47,3 @@ export type TCollaborativeEditorHookProps = TCoreHookProps &
| "tabIndex"
> &
Pick;
-
-export type TReadOnlyEditorHookProps = TCoreHookProps &
- Pick &
- Pick;
diff --git a/packages/editor/src/core/types/mention.ts b/packages/editor/src/core/types/mention.ts
index 529d64bde18..f8519194145 100644
--- a/packages/editor/src/core/types/mention.ts
+++ b/packages/editor/src/core/types/mention.ts
@@ -18,11 +18,8 @@ export type TMentionSection = {
export type TMentionComponentProps = Pick;
-export type TReadOnlyMentionHandler = {
- renderComponent: (props: TMentionComponentProps) => React.ReactNode;
+export type TMentionHandler = {
getMentionedEntityDetails?: (entity_identifier: string) => { display_name: string } | undefined;
-};
-
-export type TMentionHandler = TReadOnlyMentionHandler & {
+ renderComponent: (props: TMentionComponentProps) => React.ReactNode;
searchCallback?: (query: string) => Promise;
};
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index 2cfc6891588..10d7bb41137 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -11,7 +11,6 @@ export {
CollaborativeDocumentEditorWithRef,
DocumentEditorWithRef,
LiteTextEditorWithRef,
- LiteTextReadOnlyEditorWithRef,
RichTextEditorWithRef,
} from "@/components/editors";
From a1e97fb719381a002d86196bb36275e529d8a285 Mon Sep 17 00:00:00 2001
From: Aaryan Khandelwal
Date: Tue, 29 Jul 2025 12:26:24 +0530
Subject: [PATCH 2/2] chore: update types
---
apps/space/core/components/editor/lite-text-editor.tsx | 2 +-
packages/editor/src/core/types/editor.ts | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/apps/space/core/components/editor/lite-text-editor.tsx b/apps/space/core/components/editor/lite-text-editor.tsx
index 9a29dac9d10..ebbfaecbec4 100644
--- a/apps/space/core/components/editor/lite-text-editor.tsx
+++ b/apps/space/core/components/editor/lite-text-editor.tsx
@@ -1,7 +1,7 @@
import React from "react";
// plane imports
import { type EditorRefApi, type ILiteTextEditorProps, LiteTextEditorWithRef, type TFileHandler } from "@plane/editor";
-import { MakeOptional } from "@plane/types";
+import type { MakeOptional } from "@plane/types";
import { cn } from "@plane/utils";
// components
import { EditorMentionsRoot, IssueCommentToolbar } from "@/components/editor";
diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts
index bbf6986067d..39744b3abfe 100644
--- a/packages/editor/src/core/types/editor.ts
+++ b/packages/editor/src/core/types/editor.ts
@@ -81,7 +81,7 @@ export type TDocumentInfo = {
words: number;
};
-export interface EditorRefApi {
+export type EditorRefApi = {
blur: () => void;
clearEditor: (emitUpdate?: boolean) => void;
emitRealTimeUpdate: (action: TDocumentEventsServer) => void;
@@ -110,7 +110,7 @@ export interface EditorRefApi {
setEditorValueAtCursorPosition: (content: string) => void;
setFocusAtPosition: (position: number) => void;
setProviderDocument: (value: Uint8Array) => void;
-}
+};
// editor props
export interface IEditorProps {