diff --git a/packages/editor/core/src/index.ts b/packages/editor/core/src/index.ts
index 8b72e0bf3..c46ad9336 100644
--- a/packages/editor/core/src/index.ts
+++ b/packages/editor/core/src/index.ts
@@ -1,9 +1,16 @@
+// styles
import "@/styles/tailwind.css";
import "@/styles/editor.css";
-export { startImageUpload } from "@/ui/plugins/upload-image";
-export { useEditor } from "@/ui/hooks/useEditor";
+// utils
export { cn } from "@/lib/utils";
export { getEditorClassNames } from "@/lib/utils";
-export { EditorContainer } from "@/ui/editor-container";
-export { EditorContentWrapper } from "@/ui/editor-content";
+export { startImageUpload } from "@/ui/plugins/upload-image";
+
+// components
+export { EditorContainer } from "@/ui/components/editor-container";
+export { EditorContentWrapper } from "@/ui/components/editor-content";
+
+// hooks
+export { useEditor } from "@/ui/hooks/useEditor";
+export { useReadOnlyEditor } from "@/ui/hooks/useReadOnlyEditor";
diff --git a/packages/editor/core/src/ui/editor-container.tsx b/packages/editor/core/src/ui/components/editor-container.tsx
similarity index 100%
rename from packages/editor/core/src/ui/editor-container.tsx
rename to packages/editor/core/src/ui/components/editor-container.tsx
diff --git a/packages/editor/core/src/ui/editor-content.tsx b/packages/editor/core/src/ui/components/editor-content.tsx
similarity index 92%
rename from packages/editor/core/src/ui/editor-content.tsx
rename to packages/editor/core/src/ui/components/editor-content.tsx
index 7b06944d8..1e56e98c9 100644
--- a/packages/editor/core/src/ui/editor-content.tsx
+++ b/packages/editor/core/src/ui/components/editor-content.tsx
@@ -1,7 +1,7 @@
import { Editor, EditorContent } from "@tiptap/react";
import { ReactNode } from "react";
import { ImageResizer } from "@/ui/extensions/image/image-resize";
-import { TableMenu } from "./menus/table-menu";
+import { TableMenu } from "@/ui/menus/table-menu";
interface EditorContentProps {
editor: Editor | null;
diff --git a/packages/editor/core/src/ui/extensions/image/read-only-image.tsx b/packages/editor/core/src/ui/extensions/image/read-only-image.tsx
new file mode 100644
index 000000000..73a763d04
--- /dev/null
+++ b/packages/editor/core/src/ui/extensions/image/read-only-image.tsx
@@ -0,0 +1,17 @@
+import Image from "@tiptap/extension-image";
+
+const ReadOnlyImageExtension = Image.extend({
+ addAttributes() {
+ return {
+ ...this.parent?.(),
+ width: {
+ default: "35%",
+ },
+ height: {
+ default: null,
+ },
+ };
+ },
+});
+
+export default ReadOnlyImageExtension;
diff --git a/packages/editor/core/src/ui/extensions/index.tsx b/packages/editor/core/src/ui/extensions/index.tsx
index b2d0a5c57..7aac1adb1 100644
--- a/packages/editor/core/src/ui/extensions/index.tsx
+++ b/packages/editor/core/src/ui/extensions/index.tsx
@@ -8,9 +8,9 @@ import TaskList from "@tiptap/extension-task-list";
import { Markdown } from "tiptap-markdown";
import Gapcursor from "@tiptap/extension-gapcursor";
-import { CustomTableCell } from "./table/table-cell";
-import { Table } from "./table/table";
-import { TableHeader } from "./table/table-header";
+import { CustomTableCell } from "@/ui/extensions/table/table-cell";
+import { Table } from "@/ui/extensions/table";
+import { TableHeader } from "@/ui/extensions/table/table-header";
import { TableRow } from "@tiptap/extension-table-row";
import ImageExtension from "@/ui/extensions/image";
diff --git a/packages/editor/core/src/ui/extensions/table/table.ts b/packages/editor/core/src/ui/extensions/table/index.ts
similarity index 100%
rename from packages/editor/core/src/ui/extensions/table/table.ts
rename to packages/editor/core/src/ui/extensions/table/index.ts
diff --git a/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx b/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx
new file mode 100644
index 000000000..5d08b867c
--- /dev/null
+++ b/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx
@@ -0,0 +1,37 @@
+import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
+import { useImperativeHandle, useRef, MutableRefObject } from "react";
+import { CoreReadOnlyEditorExtensions } from "@/ui/read-only/extensions";
+import { CoreReadOnlyEditorProps } from "@/ui/read-only/props";
+
+interface CustomReadOnlyEditorProps {
+ value: string;
+ forwardedRef?: any;
+}
+
+export const useReadOnlyEditor = ({ value, forwardedRef }: CustomReadOnlyEditorProps) => {
+ const editor = useCustomEditor({
+ editable: false,
+ content: (typeof value === "string" && value.trim() !== "") ? value : "
",
+ editorProps: CoreReadOnlyEditorProps,
+ extensions: CoreReadOnlyEditorExtensions,
+ });
+
+ const editorRef: MutableRefObject = useRef(null);
+ editorRef.current = editor;
+
+ useImperativeHandle(forwardedRef, () => ({
+ clearEditor: () => {
+ editorRef.current?.commands.clearContent();
+ },
+ setEditorValue: (content: string) => {
+ editorRef.current?.commands.setContent(content);
+ },
+ }));
+
+
+ if (!editor) {
+ return null;
+ }
+
+ return editor;
+};
diff --git a/packages/editor/core/src/ui/read-only/extensions.tsx b/packages/editor/core/src/ui/read-only/extensions.tsx
new file mode 100644
index 000000000..f879b2744
--- /dev/null
+++ b/packages/editor/core/src/ui/read-only/extensions.tsx
@@ -0,0 +1,92 @@
+import StarterKit from "@tiptap/starter-kit";
+import TiptapLink from "@tiptap/extension-link";
+import TiptapUnderline from "@tiptap/extension-underline";
+import TextStyle from "@tiptap/extension-text-style";
+import { Color } from "@tiptap/extension-color";
+import TaskItem from "@tiptap/extension-task-item";
+import TaskList from "@tiptap/extension-task-list";
+import { Markdown } from "tiptap-markdown";
+import Gapcursor from "@tiptap/extension-gapcursor";
+
+import { CustomTableCell } from "@/ui/extensions/table/table-cell";
+import { Table } from "@/ui/extensions/table";
+import { TableHeader } from "@/ui/extensions/table/table-header";
+import { TableRow } from "@tiptap/extension-table-row";
+
+import isValidHttpUrl from "@/ui/menus/bubble-menu/utils";
+import ReadOnlyImageExtension from "@/ui/extensions/image/read-only-image";
+
+export const CoreReadOnlyEditorExtensions = [
+ StarterKit.configure({
+ bulletList: {
+ HTMLAttributes: {
+ class: "list-disc list-outside leading-3 -mt-2",
+ },
+ },
+ orderedList: {
+ HTMLAttributes: {
+ class: "list-decimal list-outside leading-3 -mt-2",
+ },
+ },
+ listItem: {
+ HTMLAttributes: {
+ class: "leading-normal -mb-2",
+ },
+ },
+ blockquote: {
+ HTMLAttributes: {
+ class: "border-l-4 border-custom-border-300",
+ },
+ },
+ code: {
+ HTMLAttributes: {
+ class:
+ "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000",
+ spellcheck: "false",
+ },
+ },
+ codeBlock: false,
+ horizontalRule: false,
+ dropcursor: {
+ color: "rgba(var(--color-text-100))",
+ width: 2,
+ },
+ gapcursor: false,
+ }),
+ Gapcursor,
+ TiptapLink.configure({
+ protocols: ["http", "https"],
+ validate: (url) => isValidHttpUrl(url),
+ HTMLAttributes: {
+ class:
+ "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
+ },
+ }),
+ ReadOnlyImageExtension.configure({
+ HTMLAttributes: {
+ class: "rounded-lg border border-custom-border-300",
+ },
+ }),
+ TiptapUnderline,
+ TextStyle,
+ Color,
+ TaskList.configure({
+ HTMLAttributes: {
+ class: "not-prose pl-2",
+ },
+ }),
+ TaskItem.configure({
+ HTMLAttributes: {
+ class: "flex items-start my-4",
+ },
+ nested: true,
+ }),
+ Markdown.configure({
+ html: true,
+ transformCopiedText: true,
+ }),
+ Table,
+ TableHeader,
+ CustomTableCell,
+ TableRow,
+ ];
diff --git a/packages/editor/core/src/ui/read-only/props.tsx b/packages/editor/core/src/ui/read-only/props.tsx
new file mode 100644
index 000000000..25db2b68c
--- /dev/null
+++ b/packages/editor/core/src/ui/read-only/props.tsx
@@ -0,0 +1,8 @@
+import { EditorProps } from "@tiptap/pm/view";
+
+export const CoreReadOnlyEditorProps: EditorProps =
+{
+ attributes: {
+ class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`,
+ },
+};
diff --git a/packages/editor/core/tsup.config.ts b/packages/editor/core/tsup.config.ts
index 907f339a1..5e89e04af 100644
--- a/packages/editor/core/tsup.config.ts
+++ b/packages/editor/core/tsup.config.ts
@@ -3,7 +3,7 @@ import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
- dts: false,
+ dts: true,
clean: false,
external: ["react"],
injectStyle: true,
diff --git a/packages/editor/lite-text-editor/src/index.ts b/packages/editor/lite-text-editor/src/index.ts
index 9238be8b9..0a276d1c0 100644
--- a/packages/editor/lite-text-editor/src/index.ts
+++ b/packages/editor/lite-text-editor/src/index.ts
@@ -1 +1,2 @@
export { LiteTextEditor, LiteTextEditorWithRef } from "@/ui";
+export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef } from "@/ui/read-only";
diff --git a/packages/editor/lite-text-editor/src/ui/read-only/index.tsx b/packages/editor/lite-text-editor/src/ui/read-only/index.tsx
new file mode 100644
index 000000000..3990cb734
--- /dev/null
+++ b/packages/editor/lite-text-editor/src/ui/read-only/index.tsx
@@ -0,0 +1,54 @@
+"use client"
+import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core';
+import * as React from 'react';
+
+interface ICoreReadOnlyEditor {
+ value: string;
+ editorContentCustomClassNames?: string;
+ noBorder?: boolean;
+ borderOnFocus?: boolean;
+ customClassName?: string;
+}
+
+interface EditorCoreProps extends ICoreReadOnlyEditor {
+ forwardedRef?: React.Ref;
+}
+
+interface EditorHandle {
+ clearEditor: () => void;
+ setEditorValue: (content: string) => void;
+}
+
+const LiteReadOnlyEditor = ({
+ editorContentCustomClassNames,
+ noBorder,
+ borderOnFocus,
+ customClassName,
+ value,
+ forwardedRef,
+}: EditorCoreProps) => {
+ const editor = useReadOnlyEditor({
+ value,
+ forwardedRef,
+ });
+
+ const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
+
+ if (!editor) return null;
+
+ return (
+
+
+
+
+
+ );
+};
+
+const LiteReadOnlyEditorWithRef = React.forwardRef((props, ref) => (
+
+));
+
+LiteReadOnlyEditorWithRef.displayName = "LiteReadOnlyEditorWithRef";
+
+export { LiteReadOnlyEditor , LiteReadOnlyEditorWithRef };
diff --git a/packages/editor/lite-text-editor/tsup.config.ts b/packages/editor/lite-text-editor/tsup.config.ts
index 907f339a1..5e89e04af 100644
--- a/packages/editor/lite-text-editor/tsup.config.ts
+++ b/packages/editor/lite-text-editor/tsup.config.ts
@@ -3,7 +3,7 @@ import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
- dts: false,
+ dts: true,
clean: false,
external: ["react"],
injectStyle: true,
diff --git a/packages/editor/rich-text-editor/src/index.ts b/packages/editor/rich-text-editor/src/index.ts
index b7ef6bbe4..dd8f35791 100644
--- a/packages/editor/rich-text-editor/src/index.ts
+++ b/packages/editor/rich-text-editor/src/index.ts
@@ -1,3 +1,4 @@
import "@/styles/github-dark.css";
export { RichTextEditor, RichTextEditorWithRef } from "@/ui";
+export { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "@/ui/read-only";
diff --git a/packages/editor/rich-text-editor/src/ui/read-only/index.tsx b/packages/editor/rich-text-editor/src/ui/read-only/index.tsx
new file mode 100644
index 000000000..dc058cf89
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/ui/read-only/index.tsx
@@ -0,0 +1,54 @@
+"use client"
+import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core';
+import * as React from 'react';
+
+interface IRichTextReadOnlyEditor {
+ value: string;
+ editorContentCustomClassNames?: string;
+ noBorder?: boolean;
+ borderOnFocus?: boolean;
+ customClassName?: string;
+}
+
+interface RichTextReadOnlyEditorProps extends IRichTextReadOnlyEditor {
+ forwardedRef?: React.Ref;
+}
+
+interface EditorHandle {
+ clearEditor: () => void;
+ setEditorValue: (content: string) => void;
+}
+
+const RichReadOnlyEditor = ({
+ editorContentCustomClassNames,
+ noBorder,
+ borderOnFocus,
+ customClassName,
+ value,
+ forwardedRef,
+}: RichTextReadOnlyEditorProps) => {
+ const editor = useReadOnlyEditor({
+ value,
+ forwardedRef,
+ });
+
+ const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
+
+ if (!editor) return null;
+
+ return (
+
+
+
+
+
+ );
+};
+
+const RichReadOnlyEditorWithRef = React.forwardRef((props, ref) => (
+
+));
+
+RichReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef";
+
+export { RichReadOnlyEditor , RichReadOnlyEditorWithRef };
diff --git a/packages/editor/rich-text-editor/tsup.config.ts b/packages/editor/rich-text-editor/tsup.config.ts
index 907f339a1..5e89e04af 100644
--- a/packages/editor/rich-text-editor/tsup.config.ts
+++ b/packages/editor/rich-text-editor/tsup.config.ts
@@ -3,7 +3,7 @@ import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
- dts: false,
+ dts: true,
clean: false,
external: ["react"],
injectStyle: true,
diff --git a/web/components/issues/comment/comment-card.tsx b/web/components/issues/comment/comment-card.tsx
index 147c49bd1..db33189f8 100644
--- a/web/components/issues/comment/comment-card.tsx
+++ b/web/components/issues/comment/comment-card.tsx
@@ -9,7 +9,7 @@ import useUser from "hooks/use-user";
// ui
import { CustomMenu, Icon } from "components/ui";
import { CommentReaction } from "components/issues";
-import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
+import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor";
// helpers
import { timeAgo } from "helpers/date-time.helper";
// types
@@ -151,12 +151,9 @@ export const CommentCard: React.FC = ({
/>
)}
-