diff --git a/packages/editor/core/package.json b/packages/editor/core/package.json
index a6a61cffb..9cf27203c 100644
--- a/packages/editor/core/package.json
+++ b/packages/editor/core/package.json
@@ -1,5 +1,5 @@
{
- "name": "@plane/editor",
+ "name": "@plane/editor-core",
"version": "0.0.1",
"description": "Rich Text Editor that powers Plane",
"main": "./dist/index.mjs",
diff --git a/packages/editor/core/src/hooks/useEditor.tsx b/packages/editor/core/src/hooks/useEditor.tsx
deleted file mode 100644
index 9d121fcff..000000000
--- a/packages/editor/core/src/hooks/useEditor.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { DeleteImage } from "@/types/delete-image";
-import { UploadImage } from "@/types/upload-image";
-import { TiptapExtensions } from "@/ui/extensions";
-import { TiptapEditorProps } from "@/ui/props";
-import { useEditor as useTiptapEditor } from "@tiptap/react";
-
-interface ITiptapEditor {
- value: string;
- uploadFile: UploadImage;
- deleteFile: DeleteImage;
- onChange?: (json: any, html: string) => void;
- setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
- setShouldShowAlert?: (showAlert: boolean) => void;
- editable?: boolean;
- debouncedUpdatesEnabled?: boolean;
- debouncedUpdates: ({ onChange, editor }: { onChange?: (json: any, html: string) => void; editor: any }) => void;
-}
-
-export const useEditor = ({ uploadFile, debouncedUpdates, setShouldShowAlert, deleteFile, setIsSubmitting, value, onChange, debouncedUpdatesEnabled, editable }: ITiptapEditor) => useTiptapEditor({
- editable: editable ?? true,
- editorProps: TiptapEditorProps(uploadFile, setIsSubmitting),
- extensions: TiptapExtensions(uploadFile, deleteFile, setIsSubmitting),
- content: (typeof value === "string" && value.trim() !== "") ? value : "
",
- onUpdate: async ({ editor }) => {
- // for instant feedback loop
- setIsSubmitting?.("submitting");
- setShouldShowAlert?.(true);
- if (debouncedUpdatesEnabled) {
- debouncedUpdates({ onChange, editor });
- } else {
- onChange?.(editor.getJSON(), editor.getHTML());
- }
- },
-});
diff --git a/packages/editor/core/src/index.ts b/packages/editor/core/src/index.ts
index 890841cb9..1fc9b618c 100644
--- a/packages/editor/core/src/index.ts
+++ b/packages/editor/core/src/index.ts
@@ -1,13 +1,10 @@
import "@/styles/tailwind.css";
import "@/styles/editor.css";
-// export { ImageResizer } from "./ui/extensions/image/image-resize";
-// export { TiptapEditorProps } from "./ui/props";
-// export { TableMenu } from "./ui/menus/table-menu";
-// export { TiptapExtensions } from "./ui/extensions";
-// export { cn } from "./lib/utils";
-// export { FixedMenu } from "./ui/menus/fixed-menu";
-// export { EditorBubbleMenu } from "./ui/menus/bubble-menu";
+export { startImageUpload } from "@/ui/plugins/upload-image";
-export { TiptapEditor, TiptapEditorWithRef } from "@/ui";
-
-export { useEditor } from "@/useEditor";
+// export { TiptapEditor, TiptapEditorWithRef } from "@/ui";
+export { useEditor } from "@/ui/hooks/useEditor";
+export { cn } from "@/lib/utils";
+export { getEditorClassNames } from "@/lib/utils";
+export { EditorContainer } from "@/ui/editor-container";
+export { EditorContentWrapper } from "@/ui/editor-content";
diff --git a/packages/editor/core/src/interfaces/index.ts b/packages/editor/core/src/interfaces/index.ts
deleted file mode 100644
index 7f5492df7..000000000
--- a/packages/editor/core/src/interfaces/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface EditorHandle {
- clearEditor: () => void;
- setEditorValue: (content: string) => void;
-}
diff --git a/packages/editor/core/src/lib/utils.ts b/packages/editor/core/src/lib/utils.ts
index 1c985922b..64b5a3db7 100644
--- a/packages/editor/core/src/lib/utils.ts
+++ b/packages/editor/core/src/lib/utils.ts
@@ -12,4 +12,18 @@ export const findTableAncestor = (
node = node.parentNode;
}
return node as HTMLTableElement;
-};
\ No newline at end of file
+};
+
+interface EditorClassNames {
+ noBorder?: boolean;
+ borderOnFocus?: boolean;
+ customClassName?: string;
+}
+
+export const getEditorClassNames = ({ noBorder, borderOnFocus, customClassName }: EditorClassNames) => cn(
+ 'relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md',
+ noBorder ? '' : 'border border-custom-border-200',
+ borderOnFocus ? 'focus:border border-custom-border-300' : 'focus:border-0',
+ customClassName
+);
+
diff --git a/packages/editor/core/src/ui/editor-container.tsx b/packages/editor/core/src/ui/editor-container.tsx
new file mode 100644
index 000000000..fca24f962
--- /dev/null
+++ b/packages/editor/core/src/ui/editor-container.tsx
@@ -0,0 +1,20 @@
+import { Editor } from "@tiptap/react";
+import { ReactNode } from "react";
+
+interface EditorContainerProps {
+ editor: Editor | null;
+ editorClassNames: string;
+ children: ReactNode;
+}
+
+export const EditorContainer = ({ editor, editorClassNames, children }: EditorContainerProps) => (
+ {
+ editor?.chain().focus().run();
+ }}
+ className={`tiptap-editor-container cursor-text ${editorClassNames}`}
+ >
+ {children}
+
+);
diff --git a/packages/editor/core/src/ui/editor-content.tsx b/packages/editor/core/src/ui/editor-content.tsx
new file mode 100644
index 000000000..7b06944d8
--- /dev/null
+++ b/packages/editor/core/src/ui/editor-content.tsx
@@ -0,0 +1,19 @@
+import { Editor, EditorContent } from "@tiptap/react";
+import { ReactNode } from "react";
+import { ImageResizer } from "@/ui/extensions/image/image-resize";
+import { TableMenu } from "./menus/table-menu";
+
+interface EditorContentProps {
+ editor: Editor | null;
+ editorContentCustomClassNames: string | undefined;
+ children?: ReactNode;
+}
+
+export const EditorContentWrapper = ({ editor, editorContentCustomClassNames = '', children }: EditorContentProps) => (
+
+
+
+ {editor?.isActive("image") &&
}
+ {children}
+
+);
diff --git a/packages/editor/core/src/ui/extensions/image/updated-image.tsx b/packages/editor/core/src/ui/extensions/image/index.tsx
similarity index 83%
rename from packages/editor/core/src/ui/extensions/image/updated-image.tsx
rename to packages/editor/core/src/ui/extensions/image/index.tsx
index 9157e8905..ac8d43597 100644
--- a/packages/editor/core/src/ui/extensions/image/updated-image.tsx
+++ b/packages/editor/core/src/ui/extensions/image/index.tsx
@@ -3,7 +3,7 @@ import TrackImageDeletionPlugin from "@/ui/plugins/delete-image";
import UploadImagesPlugin from "@/ui/plugins/upload-image";
import { DeleteImage } from "@/types/delete-image";
-const UpdatedImage = (deleteImage: DeleteImage) => Image.extend({
+const ImageExtension = (deleteImage: DeleteImage) => Image.extend({
addProseMirrorPlugins() {
return [UploadImagesPlugin(), TrackImageDeletionPlugin(deleteImage)];
},
@@ -20,4 +20,4 @@ const UpdatedImage = (deleteImage: DeleteImage) => Image.extend({
},
});
-export default UpdatedImage;
+export default ImageExtension;
diff --git a/packages/editor/core/src/ui/extensions/index-new.tsx b/packages/editor/core/src/ui/extensions/index-new.tsx
deleted file mode 100644
index 27639fe06..000000000
--- a/packages/editor/core/src/ui/extensions/index-new.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-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 UpdatedImage from "@/ui/extensions/image/updated-image";
-
-import { DeleteImage } from "@/types/delete-image";
-
-import isValidHttpUrl from "@/ui/menus/bubble-menu/utils"
-
-export const TiptapExtensions = (
- deleteFile: DeleteImage,
-) => [
- 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",
- },
- }),
- UpdatedImage(deleteFile).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,
- }),
- ];
diff --git a/packages/editor/core/src/ui/extensions/index.tsx b/packages/editor/core/src/ui/extensions/index.tsx
index 1c1132444..65fb7582d 100644
--- a/packages/editor/core/src/ui/extensions/index.tsx
+++ b/packages/editor/core/src/ui/extensions/index.tsx
@@ -1,40 +1,26 @@
import StarterKit from "@tiptap/starter-kit";
-import HorizontalRule from "@tiptap/extension-horizontal-rule";
import TiptapLink from "@tiptap/extension-link";
-import Placeholder from "@tiptap/extension-placeholder";
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 Highlight from "@tiptap/extension-highlight";
-import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
-import { InputRule } from "@tiptap/core";
import Gapcursor from "@tiptap/extension-gapcursor";
-import { Table } from "@/ui/extensions/table/table";
-import { TableHeader } from "@/ui/extensions/table/table-header";
-import { TableRow } from "@tiptap/extension-table-row";
-import { CustomTableCell } from "@/ui/extensions/table/table-cell";
-import UpdatedImage from "@/ui/extensions/image/updated-image";
-import SlashCommand from "@/ui/extensions/slash-command";
+import { CustomTableCell } from "./table/table-cell";
+import { Table } from "./table/table";
+import { TableHeader } from "./table/table-header";
+import { TableRow } from "@tiptap/extension-table-row";
+
+import ImageExtension from "@/ui/extensions/image";
import { DeleteImage } from "@/types/delete-image";
-import { UploadImage } from "@/types/upload-image";
import isValidHttpUrl from "@/ui/menus/bubble-menu/utils"
-import ts from "highlight.js/lib/languages/typescript";
-import { lowlight } from "lowlight/lib/core";
-import "highlight.js/styles/github-dark.css";
-
-lowlight.registerLanguage("ts", ts);
-
export const TiptapExtensions = (
- uploadFile: UploadImage,
deleteFile: DeleteImage,
- setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
) => [
StarterKit.configure({
bulletList: {
@@ -72,32 +58,6 @@ export const TiptapExtensions = (
},
gapcursor: false,
}),
- CodeBlockLowlight.configure({
- lowlight,
- }),
- HorizontalRule.extend({
- addInputRules() {
- return [
- new InputRule({
- find: /^(?:---|—-|___\s|\*\*\*\s)$/,
- handler: ({ state, range, commands }) => {
- commands.splitBlock();
-
- const attributes = {};
- const { tr } = state;
- const start = range.from;
- const end = range.to;
- // @ts-ignore
- tr.replaceWith(start - 1, end, this.type.create(attributes));
- },
- }),
- ];
- },
- }).configure({
- HTMLAttributes: {
- class: "mb-6 border-t border-custom-border-300",
- },
- }),
Gapcursor,
TiptapLink.configure({
protocols: ["http", "https"],
@@ -107,31 +67,14 @@ export const TiptapExtensions = (
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
- UpdatedImage(deleteFile).configure({
+ ImageExtension(deleteFile).configure({
HTMLAttributes: {
class: "rounded-lg border border-custom-border-300",
},
}),
- Placeholder.configure({
- placeholder: ({ node }) => {
- if (node.type.name === "heading") {
- return `Heading ${node.attrs.level}`;
- }
- if (node.type.name === "image" || node.type.name === "table") {
- return "";
- }
-
- return "Press '/' for commands...";
- },
- includeChildren: true,
- }),
- SlashCommand(uploadFile, setIsSubmitting),
TiptapUnderline,
TextStyle,
Color,
- Highlight.configure({
- multicolor: true,
- }),
TaskList.configure({
HTMLAttributes: {
class: "not-prose pl-2",
diff --git a/packages/editor/core/src/ui/hooks/useEditor.tsx b/packages/editor/core/src/ui/hooks/useEditor.tsx
new file mode 100644
index 000000000..cec52d25f
--- /dev/null
+++ b/packages/editor/core/src/ui/hooks/useEditor.tsx
@@ -0,0 +1,70 @@
+import { useEditor as useCustomEditor, Editor, Extension } from "@tiptap/react";
+import { useImperativeHandle, useRef, MutableRefObject, forwardRef } from "react";
+import { useDebouncedCallback } from "use-debounce";
+import { UploadImage } from '@/types/upload-image';
+import { DeleteImage } from '@/types/delete-image';
+import { TiptapEditorProps } from "../props";
+import { TiptapExtensions } from "../extensions";
+import { EditorProps } from '@tiptap/pm/view';
+
+const DEBOUNCE_DELAY = 1500;
+
+interface CustomEditorProps {
+ editable?: boolean;
+ uploadFile: UploadImage;
+ setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
+ setShouldShowAlert?: (showAlert: boolean) => void;
+ value: string;
+ deleteFile: DeleteImage;
+ debouncedUpdatesEnabled?: boolean;
+ onChange?: (json: any, html: string) => void;
+ extensions?: Extension[];
+ editorProps?: EditorProps;
+ forwardedRef?: any;
+}
+
+export const useEditor = ({ uploadFile, editable, deleteFile, editorProps = {}, value, extensions = [], onChange, setIsSubmitting, debouncedUpdatesEnabled, forwardedRef, setShouldShowAlert, }: CustomEditorProps) => {
+ const editor = useCustomEditor({
+ editable: editable ?? true,
+ editorProps: {
+ ...TiptapEditorProps(uploadFile, setIsSubmitting),
+ ...editorProps,
+ },
+ extensions: [...TiptapExtensions(deleteFile), ...extensions],
+ content: (typeof value === "string" && value.trim() !== "") ? value : "",
+ onUpdate: async ({ editor }) => {
+ // for instant feedback loop
+ setIsSubmitting?.("submitting");
+ setShouldShowAlert?.(true);
+ if (debouncedUpdatesEnabled) {
+ debouncedUpdates({ onChange: onChange, editor });
+ } else {
+ onChange?.(editor.getJSON(), editor.getHTML());
+ }
+ },
+ });
+
+ const editorRef: MutableRefObject = useRef(null);
+ editorRef.current = editor;
+
+ useImperativeHandle(forwardedRef, () => ({
+ clearEditor: () => {
+ editorRef.current?.commands.clearContent();
+ },
+ setEditorValue: (content: string) => {
+ editorRef.current?.commands.setContent(content);
+ },
+ }));
+
+ const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => {
+ if (onChange) {
+ onChange(editor.getJSON(), editor.getHTML());
+ }
+ }, DEBOUNCE_DELAY);
+
+ if (!editor) {
+ return null;
+ }
+
+ return editor;
+};
diff --git a/packages/editor/core/src/ui/index-new.tsx b/packages/editor/core/src/ui/index-new.tsx
deleted file mode 100644
index b6e42348b..000000000
--- a/packages/editor/core/src/ui/index-new.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-"use client"
-import * as React from 'react';
-import { useImperativeHandle, useRef, forwardRef } from "react";
-import { useEditor, EditorContent, Editor, Extension } from "@tiptap/react";
-import { useDebouncedCallback } from "use-debounce";
-import { TableMenu } from '@/ui/menus/table-menu';
-import { TiptapExtensions } from '@/ui/extensions';
-import { EditorBubbleMenu } from '@/ui/menus/bubble-menu';
-import { ImageResizer } from '@/ui/extensions/image/image-resize';
-import { TiptapEditorProps } from '@/ui/props';
-import { UploadImage } from '@/types/upload-image';
-import { DeleteImage } from '@/types/delete-image';
-import { cn } from '@/lib/utils';
-import { FixedMenu } from './menus/fixed-menu';
-import { EditorProps } from '@tiptap/pm/view';
-
-interface ITiptapEditor {
- value: string;
- uploadFile: UploadImage;
- deleteFile: DeleteImage;
- noBorder?: boolean;
- borderOnFocus?: boolean;
- customClassName?: string;
- editorContentCustomClassNames?: string;
- onChange?: (json: any, html: string) => void;
- setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
- setShouldShowAlert?: (showAlert: boolean) => void;
- editable?: boolean;
- forwardedRef?: any;
- debouncedUpdatesEnabled?: boolean;
- accessValue: string;
- onAccessChange: (accessKey: string) => void;
- commentAccess: {
- icon: string;
- key: string;
- label: "Private" | "Public";
- }[];
- extensions?: Extension[];
- editorProps?: EditorProps;
-}
-
-interface TiptapProps extends ITiptapEditor {
- forwardedRef?: React.Ref;
-}
-
-interface EditorHandle {
- clearEditor: () => void;
- setEditorValue: (content: string) => void;
-}
-
-const DEBOUNCE_DELAY = 1500;
-
-const TiptapEditor = ({
- onChange,
- debouncedUpdatesEnabled,
- editable,
- setIsSubmitting,
- setShouldShowAlert,
- editorContentCustomClassNames,
- value,
- uploadFile,
- extensions = [],
- editorProps = {},
- deleteFile,
- noBorder,
- borderOnFocus,
- customClassName,
- forwardedRef,
- accessValue,
- onAccessChange,
- commentAccess,
-}: TiptapProps) => {
- const editor = useEditor({
- editable: editable ?? true,
- editorProps: {
- ...TiptapEditorProps(uploadFile, setIsSubmitting),
- ...editorProps,
- },
- extensions: [...TiptapExtensions(uploadFile, deleteFile, setIsSubmitting), ...extensions],
- content: (typeof value === "string" && value.trim() !== "") ? value : "",
- onUpdate: async ({ editor }) => {
- // for instant feedback loop
- setIsSubmitting?.("submitting");
- setShouldShowAlert?.(true);
- if (debouncedUpdatesEnabled) {
- debouncedUpdates({ onChange, editor });
- } else {
- onChange?.(editor.getJSON(), editor.getHTML());
- }
- },
- });
-
- const editorRef: React.MutableRefObject = useRef(null);
- editorRef.current = editor;
-
- useImperativeHandle(forwardedRef, () => ({
- clearEditor: () => {
- editorRef.current?.commands.clearContent();
- },
- setEditorValue: (content: string) => {
- editorRef.current?.commands.setContent(content);
- },
- }));
-
- const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => {
- if (onChange) {
- onChange(editor.getJSON(), editor.getHTML());
- }
- }, DEBOUNCE_DELAY);
-
- const editorClassNames = cn(
- 'relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md',
- noBorder ? '' : 'border border-custom-border-200',
- borderOnFocus ? 'focus:border border-custom-border-300' : 'focus:border-0',
- customClassName
- );
-
- if (!editor) return null;
-
- return (
- {
- editor?.chain().focus().run();
- }}
- className={`tiptap-editor-container cursor-text ${editorClassNames}`}
- >
-
-
-
-
- {editor?.isActive("image") &&
}
-
- {editor && editable !== false &&
- (
-
-
)
- }
-
-
- );
-};
-
-const TiptapEditorWithRef = forwardRef((props, ref) => (
-
-));
-
-TiptapEditorWithRef.displayName = "TiptapEditorWithRef";
-
-export { TiptapEditor, TiptapEditorWithRef };
diff --git a/packages/editor/core/src/ui/index.tsx b/packages/editor/core/src/ui/index.tsx
index 28808f649..ae2654e1c 100644
--- a/packages/editor/core/src/ui/index.tsx
+++ b/packages/editor/core/src/ui/index.tsx
@@ -1,17 +1,13 @@
"use client"
import * as React from 'react';
-import { useImperativeHandle, useRef, forwardRef } from "react";
-import { useEditor, EditorContent, Editor } from "@tiptap/react";
-import { useDebouncedCallback } from "use-debounce";
-import { TableMenu } from '@/ui/menus/table-menu';
-import { TiptapExtensions } from '@/ui/extensions';
-import { EditorBubbleMenu } from '@/ui/menus/bubble-menu';
-import { ImageResizer } from '@/ui/extensions/image/image-resize';
-import { TiptapEditorProps } from '@/ui/props';
+import { Extension } from "@tiptap/react";
import { UploadImage } from '@/types/upload-image';
import { DeleteImage } from '@/types/delete-image';
-import { cn } from '@/lib/utils';
-import { FixedMenu } from './menus/fixed-menu';
+import { getEditorClassNames } from '@/lib/utils';
+import { EditorProps } from '@tiptap/pm/view';
+import { useEditor } from './hooks/useEditor';
+import { EditorContainer } from '@/ui/editor-container';
+import { EditorContentWrapper } from '@/ui/editor-content';
interface ITiptapEditor {
value: string;
@@ -34,6 +30,8 @@ interface ITiptapEditor {
key: string;
label: "Private" | "Public";
}[];
+ extensions?: Extension[];
+ editorProps?: EditorProps;
}
interface TiptapProps extends ITiptapEditor {
@@ -45,8 +43,6 @@ interface EditorHandle {
setEditorValue: (content: string) => void;
}
-const DEBOUNCE_DELAY = 1500;
-
const TiptapEditor = ({
onChange,
debouncedUpdatesEnabled,
@@ -61,79 +57,33 @@ const TiptapEditor = ({
borderOnFocus,
customClassName,
forwardedRef,
- accessValue,
- onAccessChange,
- commentAccess,
}: TiptapProps) => {
const editor = useEditor({
- editable: editable ?? true,
- editorProps: TiptapEditorProps(uploadFile, setIsSubmitting),
- extensions: TiptapExtensions(uploadFile, deleteFile, setIsSubmitting),
- content: (typeof value === "string" && value.trim() !== "") ? value : "",
- onUpdate: async ({ editor }) => {
- // for instant feedback loop
- setIsSubmitting?.("submitting");
- setShouldShowAlert?.(true);
- if (debouncedUpdatesEnabled) {
- debouncedUpdates({ onChange, editor });
- } else {
- onChange?.(editor.getJSON(), editor.getHTML());
- }
- },
+ onChange,
+ debouncedUpdatesEnabled,
+ editable,
+ setIsSubmitting,
+ setShouldShowAlert,
+ value,
+ uploadFile,
+ deleteFile,
+ forwardedRef,
});
- const editorRef: React.MutableRefObject = useRef(null);
- editorRef.current = editor;
-
- useImperativeHandle(forwardedRef, () => ({
- clearEditor: () => {
- editorRef.current?.commands.clearContent();
- },
- setEditorValue: (content: string) => {
- editorRef.current?.commands.setContent(content);
- },
- }));
-
- const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => {
- if (onChange) {
- onChange(editor.getJSON(), editor.getHTML());
- }
- }, DEBOUNCE_DELAY);
-
- const editorClassNames = cn(
- 'relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md',
- noBorder ? '' : 'border border-custom-border-200',
- borderOnFocus ? 'focus:border border-custom-border-300' : 'focus:border-0',
- customClassName
- );
+ const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
if (!editor) return null;
return (
- {
- editor?.chain().focus().run();
- }}
- className={`tiptap-editor-container cursor-text ${editorClassNames}`}
- >
+
-
-
-
- {editor?.isActive("image") &&
}
-
- {editor && editable !== false &&
- (
-
-
)
- }
+
-
+
);
};
-const TiptapEditorWithRef = forwardRef((props, ref) => (
+const TiptapEditorWithRef = React.forwardRef((props, ref) => (
));
diff --git a/packages/editor/core/src/useEditor.tsx b/packages/editor/core/src/useEditor.tsx
deleted file mode 100644
index 552c0ca21..000000000
--- a/packages/editor/core/src/useEditor.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- useEditor as useEditorCore,
-} from "@tiptap/react";
-import { findTableAncestor } from "@/lib/utils";
-
-export const useEditor = (props: any) => useEditorCore({
- editorProps: {
- attributes: {
- class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`,
- },
- handleDOMEvents: {
- keydown: (_view, event) => {
- // prevent default event listeners from firing when slash command is active
- if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
- const slashCommand = document.querySelector("#slash-command");
- if (slashCommand) {
- return true;
- }
- }
- },
- },
- handlePaste: () => {
- if (typeof window !== "undefined") {
- const selection: any = window?.getSelection();
- if (selection.rangeCount !== 0) {
- const range = selection.getRangeAt(0);
- if (findTableAncestor(range.startContainer)) {
- return;
- }
- }
- }
- },
- },
- ...props,
-});
diff --git a/packages/editor/lite-text-editor/package.json b/packages/editor/lite-text-editor/package.json
index fe1d7ee01..f2805da99 100644
--- a/packages/editor/lite-text-editor/package.json
+++ b/packages/editor/lite-text-editor/package.json
@@ -24,28 +24,6 @@
"react": "^18.2.0"
},
"dependencies": {
- "@blueprintjs/popover2": "^2.0.10",
- "@radix-ui/react-slot": "^1.0.2",
- "@tiptap/core": "^2.1.7",
- "@tiptap/extension-code-block-lowlight": "^2.0.4",
- "@tiptap/extension-color": "^2.1.11",
- "@tiptap/extension-highlight": "^2.1.7",
- "@tiptap/extension-horizontal-rule": "^2.1.7",
- "@tiptap/extension-image": "^2.1.7",
- "@tiptap/extension-link": "^2.1.7",
- "@tiptap/extension-placeholder": "2.0.3",
- "@tiptap/extension-table": "^2.1.6",
- "@tiptap/extension-table-cell": "^2.1.6",
- "@tiptap/extension-table-header": "^2.1.6",
- "@tiptap/extension-table-row": "^2.1.6",
- "@tiptap/extension-task-item": "^2.1.7",
- "@tiptap/extension-task-list": "^2.1.7",
- "@tiptap/extension-text-style": "^2.1.11",
- "@tiptap/extension-underline": "^2.1.7",
- "@tiptap/pm": "^2.1.7",
- "@tiptap/react": "^2.1.7",
- "@tiptap/starter-kit": "^2.1.10",
- "@tiptap/suggestion": "^2.1.7",
"@types/node": "18.15.3",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
@@ -64,7 +42,8 @@
"tailwind-merge": "^1.14.0",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.2",
- "use-debounce": "^9.0.4"
+ "use-debounce": "^9.0.4",
+ "@plane/editor-core": "*"
},
"devDependencies": {
"@types/react": "^18.2.5",
diff --git a/packages/editor/lite-text-editor/src/index.ts b/packages/editor/lite-text-editor/src/index.ts
new file mode 100644
index 000000000..9238be8b9
--- /dev/null
+++ b/packages/editor/lite-text-editor/src/index.ts
@@ -0,0 +1 @@
+export { LiteTextEditor, LiteTextEditorWithRef } from "@/ui";
diff --git a/packages/editor/lite-text-editor/src/ui/index.tsx b/packages/editor/lite-text-editor/src/ui/index.tsx
new file mode 100644
index 000000000..ce87a39b3
--- /dev/null
+++ b/packages/editor/lite-text-editor/src/ui/index.tsx
@@ -0,0 +1,96 @@
+"use client"
+import * as React from 'react';
+import { EditorContainer, EditorContentWrapper, getEditorClassNames, useEditor } from '@plane/editor-core';
+import { FixedMenu } from './menus/fixed-menu';
+
+export type UploadImage = (file: File) => Promise;
+export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise;
+
+interface ITiptapEditor {
+ value: string;
+ uploadFile: UploadImage;
+ deleteFile: DeleteImage;
+ noBorder?: boolean;
+ borderOnFocus?: boolean;
+ customClassName?: string;
+ editorContentCustomClassNames?: string;
+ onChange?: (json: any, html: string) => void;
+ setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
+ setShouldShowAlert?: (showAlert: boolean) => void;
+ editable?: boolean;
+ forwardedRef?: any;
+ debouncedUpdatesEnabled?: boolean;
+ commentAccessSpecifier?: {
+ accessValue: string,
+ onAccessChange: (accessKey: string) => void,
+ showAccessSpecifier: boolean,
+ commentAccess: {
+ icon: string;
+ key: string;
+ label: "Private" | "Public";
+ }[]
+ }
+}
+
+interface TiptapProps extends ITiptapEditor {
+ forwardedRef?: React.Ref;
+}
+
+interface EditorHandle {
+ clearEditor: () => void;
+ setEditorValue: (content: string) => void;
+}
+
+const LiteTextEditor = ({
+ onChange,
+ debouncedUpdatesEnabled,
+ editable,
+ setIsSubmitting,
+ setShouldShowAlert,
+ editorContentCustomClassNames,
+ value,
+ uploadFile,
+ deleteFile,
+ noBorder,
+ borderOnFocus,
+ customClassName,
+ forwardedRef,
+ commentAccessSpecifier,
+}: TiptapProps) => {
+ const editor = useEditor({
+ onChange,
+ debouncedUpdatesEnabled,
+ editable,
+ setIsSubmitting,
+ setShouldShowAlert,
+ value,
+ uploadFile,
+ deleteFile,
+ forwardedRef,
+ });
+
+ const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
+
+ if (!editor) return null;
+
+ return (
+
+
+
+ {(editable !== false) &&
+ (
+
+
)
+ }
+
+
+ );
+};
+
+const LiteTextEditorWithRef = React.forwardRef((props, ref) => (
+
+));
+
+LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef";
+
+export { LiteTextEditor, LiteTextEditorWithRef };
diff --git a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx
new file mode 100644
index 000000000..c0006b3f2
--- /dev/null
+++ b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+
+type Props = {
+ iconName: string;
+ className?: string;
+};
+
+export const Icon: React.FC = ({ iconName, className = "" }) => (
+
+ {iconName}
+
+);
+
diff --git a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx
new file mode 100644
index 000000000..0cece1d58
--- /dev/null
+++ b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx
@@ -0,0 +1,115 @@
+import { Editor } from "@tiptap/react";
+import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, CodeIcon } from "lucide-react";
+
+import { cn } from "@plane/editor-core";
+import { Tooltip } from "./tooltip";
+import { Icon } from "./icon";
+
+export interface BubbleMenuItem {
+ name: string;
+ isActive: () => boolean;
+ command: () => void;
+ icon: typeof BoldIcon;
+}
+
+type EditorBubbleMenuProps = {
+ editor: Editor;
+ commentAccessSpecifier?: {
+ accessValue: string,
+ onAccessChange: (accessKey: string) => void,
+ showAccessSpecifier: boolean,
+ commentAccess: {
+ icon: string;
+ key: string;
+ label: "Private" | "Public";
+ }[] | undefined;
+ }
+}
+
+export const FixedMenu = (props: EditorBubbleMenuProps) => {
+ const items: BubbleMenuItem[] = [
+ {
+ name: "bold",
+ isActive: () => props.editor?.isActive("bold"),
+ command: () => props.editor?.chain().focus().toggleBold().run(),
+ icon: BoldIcon,
+ },
+ {
+ name: "italic",
+ isActive: () => props.editor?.isActive("italic"),
+ command: () => props.editor?.chain().focus().toggleItalic().run(),
+ icon: ItalicIcon,
+ },
+ {
+ name: "underline",
+ isActive: () => props.editor?.isActive("underline"),
+ command: () => props.editor?.chain().focus().toggleUnderline().run(),
+ icon: UnderlineIcon,
+ },
+ {
+ name: "strike",
+ isActive: () => props.editor?.isActive("strike"),
+ command: () => props.editor?.chain().focus().toggleStrike().run(),
+ icon: StrikethroughIcon,
+ },
+ {
+ name: "code",
+ isActive: () => props.editor?.isActive("code"),
+ command: () => props.editor?.chain().focus().toggleCode().run(),
+ icon: CodeIcon,
+ },
+ ];
+
+ const handleAccessChange = (accessKey: string) => {
+ props.commentAccessSpecifier?.onAccessChange(accessKey);
+ };
+
+
+ return (
+
+
+ {props.commentAccessSpecifier && (
+ {props?.commentAccessSpecifier.commentAccess?.map((access) => (
+
+
+
+ ))}
+
)}
+ {items.map((item, index) => (
+
+ ))}
+
+
+ );
+};
diff --git a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/tooltip.tsx b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/tooltip.tsx
new file mode 100644
index 000000000..f29d8a491
--- /dev/null
+++ b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/tooltip.tsx
@@ -0,0 +1,77 @@
+import * as React from 'react';
+
+// next-themes
+import { useTheme } from "next-themes";
+// tooltip2
+import { Tooltip2 } from "@blueprintjs/popover2";
+
+type Props = {
+ tooltipHeading?: string;
+ tooltipContent: string | React.ReactNode;
+ position?:
+ | "top"
+ | "right"
+ | "bottom"
+ | "left"
+ | "auto"
+ | "auto-end"
+ | "auto-start"
+ | "bottom-left"
+ | "bottom-right"
+ | "left-bottom"
+ | "left-top"
+ | "right-bottom"
+ | "right-top"
+ | "top-left"
+ | "top-right";
+ children: JSX.Element;
+ disabled?: boolean;
+ className?: string;
+ openDelay?: number;
+ closeDelay?: number;
+};
+
+export const Tooltip: React.FC = ({
+ tooltipHeading,
+ tooltipContent,
+ position = "top",
+ children,
+ disabled = false,
+ className = "",
+ openDelay = 200,
+ closeDelay,
+}) => {
+ const { theme } = useTheme();
+
+ return (
+
+ {tooltipHeading && (
+
+ {tooltipHeading}
+
+ )}
+ {tooltipContent}
+
+ }
+ position={position}
+ renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) =>
+ React.cloneElement(children, { ref: eleReference, ...tooltipProps, ...children.props })
+ }
+ />
+ );
+};
diff --git a/packages/editor/rich-text-editor/package.json b/packages/editor/rich-text-editor/package.json
index 3dc8f2b27..27ca22ac4 100644
--- a/packages/editor/rich-text-editor/package.json
+++ b/packages/editor/rich-text-editor/package.json
@@ -24,49 +24,18 @@
"react": "^18.2.0"
},
"dependencies": {
- "@blueprintjs/popover2": "^2.0.10",
- "@radix-ui/react-slot": "^1.0.2",
- "@tiptap/core": "^2.1.7",
- "@tiptap/extension-code-block-lowlight": "^2.0.4",
- "@tiptap/extension-color": "^2.1.11",
- "@tiptap/extension-highlight": "^2.1.7",
- "@tiptap/extension-horizontal-rule": "^2.1.7",
- "@tiptap/extension-image": "^2.1.7",
- "@tiptap/extension-link": "^2.1.7",
- "@tiptap/extension-placeholder": "2.0.3",
- "@tiptap/extension-table": "^2.1.6",
- "@tiptap/extension-table-cell": "^2.1.6",
- "@tiptap/extension-table-header": "^2.1.6",
- "@tiptap/extension-table-row": "^2.1.6",
- "@tiptap/extension-task-item": "^2.1.7",
- "@tiptap/extension-task-list": "^2.1.7",
- "@tiptap/extension-text-style": "^2.1.11",
- "@tiptap/extension-underline": "^2.1.7",
- "@tiptap/pm": "^2.1.7",
- "@tiptap/react": "^2.1.7",
- "@tiptap/starter-kit": "^2.1.10",
- "@tiptap/suggestion": "^2.1.7",
- "@types/node": "18.15.3",
- "@types/react": "18.0.28",
- "@types/react-dom": "18.0.11",
"class-variance-authority": "^0.7.0",
"clsx": "^1.2.1",
- "eslint": "8.36.0",
- "eslint-config-next": "13.2.4",
- "eventsource-parser": "^0.1.0",
"lowlight": "^2.9.0",
"lucide-react": "^0.244.0",
"next": "12.3.2",
- "next-themes": "^0.2.1",
"react": "18.2.0",
"react-dom": "18.2.0",
- "react-markdown": "^8.0.7",
- "tailwind-merge": "^1.14.0",
- "tippy.js": "^6.3.7",
- "tiptap-markdown": "^0.8.2",
- "use-debounce": "^9.0.4"
+ "@plane/editor-core": "*"
},
"devDependencies": {
+ "@types/node": "18.15.3",
+ "@types/react-dom": "18.0.11",
"@types/react": "^18.2.5",
"eslint": "^7.32.0",
"postcss": "^8.4.29",
diff --git a/packages/editor/rich-text-editor/src/extensions.tsx b/packages/editor/rich-text-editor/src/extensions.tsx
deleted file mode 100644
index 85087e5e1..000000000
--- a/packages/editor/rich-text-editor/src/extensions.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import HorizontalRule from "@tiptap/extension-horizontal-rule";
-import { TableRow } from "@tiptap/extension-table-row";
-import Placeholder from "@tiptap/extension-placeholder";
-import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
-import { InputRule } from "@tiptap/core";
-
-import ts from "highlight.js/lib/languages/typescript";
-import { lowlight } from "lowlight/lib/core";
-import "highlight.js/styles/github-dark.css";
-import { Table } from "./table/table";
-import { TableHeader } from "./table/table-header";
-import { CustomTableCell } from "./table/table-cell";
-import SlashCommand from "./slash-command";
-import { UploadImage } from "./types/upload-image";
-
-lowlight.registerLanguage("ts", ts);
-
-export const TiptapExtensions = (
- uploadFile: UploadImage,
- setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
-) => [
- CodeBlockLowlight.configure({
- lowlight,
- }),
- HorizontalRule.extend({
- addInputRules() {
- return [
- new InputRule({
- find: /^(?:---|—-|___\s|\*\*\*\s)$/,
- handler: ({ state, range, commands }) => {
- commands.splitBlock();
-
- const attributes = {};
- const { tr } = state;
- const start = range.from;
- const end = range.to;
- // @ts-ignore
- tr.replaceWith(start - 1, end, this.type.create(attributes));
- },
- }),
- ];
- },
- }).configure({
- HTMLAttributes: {
- class: "mb-6 border-t border-custom-border-300",
- },
- }),
- Placeholder.configure({
- placeholder: ({ node }) => {
- if (node.type.name === "heading") {
- return `Heading ${node.attrs.level}`;
- }
- if (node.type.name === "image" || node.type.name === "table") {
- return "";
- }
-
- return "Press '/' for commands...";
- },
- includeChildren: true,
- }),
- SlashCommand(uploadFile, setIsSubmitting),
- Table,
- TableHeader,
- CustomTableCell,
- TableRow,
- ];
diff --git a/packages/editor/rich-text-editor/src/index.ts b/packages/editor/rich-text-editor/src/index.ts
new file mode 100644
index 000000000..eeca9f837
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/index.ts
@@ -0,0 +1 @@
+export { RichTextEditor, RichTextEditorWithRef } from "@/ui";
diff --git a/packages/editor/rich-text-editor/src/index.tsx b/packages/editor/rich-text-editor/src/index.tsx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/packages/editor/rich-text-editor/src/slash-command.tsx b/packages/editor/rich-text-editor/src/slash-command.tsx
deleted file mode 100644
index 844d4c55a..000000000
--- a/packages/editor/rich-text-editor/src/slash-command.tsx
+++ /dev/null
@@ -1,363 +0,0 @@
-import { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react";
-import { Editor, Range, Extension } from "@tiptap/core";
-import Suggestion from "@tiptap/suggestion";
-import { ReactRenderer } from "@tiptap/react";
-import tippy from "tippy.js";
-import {
- Heading1,
- Heading2,
- Heading3,
- List,
- ListOrdered,
- Text,
- TextQuote,
- Code,
- MinusSquare,
- CheckSquare,
- ImageIcon,
- Table,
-} from "lucide-react";
-import { startImageUpload } from "@/ui/plugins/upload-image";
-import { cn } from "@/lib/utils";
-import { UploadImage } from "@/types/upload-image";
-
-interface CommandItemProps {
- title: string;
- description: string;
- icon: ReactNode;
-}
-
-interface CommandProps {
- editor: Editor;
- range: Range;
-}
-
-const Command = Extension.create({
- name: "slash-command",
- addOptions() {
- return {
- suggestion: {
- char: "/",
- command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => {
- props.command({ editor, range });
- },
- },
- };
- },
- addProseMirrorPlugins() {
- return [
- Suggestion({
- editor: this.editor,
- allow({ editor }) {
- return !editor.isActive("table");
- },
- ...this.options.suggestion,
- }),
- ];
- },
-});
-
-const getSuggestionItems =
- (
- uploadFile: UploadImage,
- setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
- ) =>
- ({ query }: { query: string }) =>
- [
- {
- title: "Text",
- description: "Just start typing with plain text.",
- searchTerms: ["p", "paragraph"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
- },
- },
- {
- title: "Heading 1",
- description: "Big section heading.",
- searchTerms: ["title", "big", "large"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
- },
- },
- {
- title: "Heading 2",
- description: "Medium section heading.",
- searchTerms: ["subtitle", "medium"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
- },
- },
- {
- title: "Heading 3",
- description: "Small section heading.",
- searchTerms: ["subtitle", "small"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
- },
- },
- {
- title: "To-do List",
- description: "Track tasks with a to-do list.",
- searchTerms: ["todo", "task", "list", "check", "checkbox"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor.chain().focus().deleteRange(range).toggleTaskList().run();
- },
- },
- {
- title: "Bullet List",
- description: "Create a simple bullet list.",
- searchTerms: ["unordered", "point"],
- icon:
,
- command: ({ editor, range }: CommandProps) => {
- // @ts-ignore
- editor.chain().focus().deleteRange(range).toggleBulletList().run();
- },
- },
- {
- title: "Divider",
- description: "Visually divide blocks",
- searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor.chain().focus().deleteRange(range).setHorizontalRule().run();
- },
- },
- {
- title: "Table",
- description: "Create a Table",
- searchTerms: ["table", "cell", "db", "data", "tabular"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor
- .chain()
- .focus()
- .deleteRange(range)
- .insertTable({ rows: 3, cols: 3, withHeaderRow: true })
- .run();
- },
- },
- {
- title: "Numbered List",
- description: "Create a list with numbering.",
- searchTerms: ["ordered"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- // @ts-ignore
- editor.chain().focus().deleteRange(range).toggleOrderedList().run();
- },
- },
- {
- title: "Quote",
- description: "Capture a quote.",
- searchTerms: ["blockquote"],
- icon: ,
- command: ({ editor, range }: CommandProps) =>
- // @ts-ignore
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
- },
- {
- title: "Code",
- description: "Capture a code snippet.",
- searchTerms: ["codeblock"],
- icon:
,
- command: ({ editor, range }: CommandProps) =>
- editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
- },
- {
- title: "Image",
- description: "Upload an image from your computer.",
- searchTerms: ["photo", "picture", "media"],
- icon: ,
- command: ({ editor, range }: CommandProps) => {
- editor.chain().focus().deleteRange(range).run();
- // upload image
- const input = document.createElement("input");
- input.type = "file";
- input.accept = "image/*";
- input.onchange = async () => {
- if (input.files?.length) {
- const file = input.files[0];
- const pos = editor.view.state.selection.from;
- startImageUpload(file, editor.view, pos, uploadFile, setIsSubmitting);
- }
- };
- input.click();
- },
- },
- ].filter((item) => {
- if (typeof query === "string" && query.length > 0) {
- const search = query.toLowerCase();
- return (
- item.title.toLowerCase().includes(search) ||
- item.description.toLowerCase().includes(search) ||
- (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search)))
- );
- }
- return true;
- });
-
-export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
- const containerHeight = container.offsetHeight;
- const itemHeight = item ? item.offsetHeight : 0;
-
- const top = item.offsetTop;
- const bottom = top + itemHeight;
-
- if (top < container.scrollTop) {
- container.scrollTop -= container.scrollTop - top + 5;
- } else if (bottom > containerHeight + container.scrollTop) {
- container.scrollTop += bottom - containerHeight - container.scrollTop + 5;
- }
-};
-
-const CommandList = ({
- items,
- command,
-}: {
- items: CommandItemProps[];
- command: any;
- editor: any;
- range: any;
-}) => {
- const [selectedIndex, setSelectedIndex] = useState(0);
-
- const selectItem = useCallback(
- (index: number) => {
- const item = items[index];
- if (item) {
- command(item);
- }
- },
- [command, items]
- );
-
- useEffect(() => {
- const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"];
- const onKeyDown = (e: KeyboardEvent) => {
- if (navigationKeys.includes(e.key)) {
- e.preventDefault();
- if (e.key === "ArrowUp") {
- setSelectedIndex((selectedIndex + items.length - 1) % items.length);
- return true;
- }
- if (e.key === "ArrowDown") {
- setSelectedIndex((selectedIndex + 1) % items.length);
- return true;
- }
- if (e.key === "Enter") {
- selectItem(selectedIndex);
- return true;
- }
- return false;
- }
- };
- document.addEventListener("keydown", onKeyDown);
- return () => {
- document.removeEventListener("keydown", onKeyDown);
- };
- }, [items, selectedIndex, setSelectedIndex, selectItem]);
-
- useEffect(() => {
- setSelectedIndex(0);
- }, [items]);
-
- const commandListContainer = useRef(null);
-
- useLayoutEffect(() => {
- const container = commandListContainer?.current;
-
- const item = container?.children[selectedIndex] as HTMLElement;
-
- if (item && container) updateScrollView(container, item);
- }, [selectedIndex]);
-
- return items.length > 0 ? (
-
- {items.map((item: CommandItemProps, index: number) => (
-
- ))}
-
- ) : null;
-};
-
-const renderItems = () => {
- let component: ReactRenderer | null = null;
- let popup: any | null = null;
-
- return {
- onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
- component = new ReactRenderer(CommandList, {
- props,
- editor: props.editor,
- });
-
- // @ts-ignore
- popup = tippy("body", {
- getReferenceClientRect: props.clientRect,
- appendTo: () => document.querySelector("#tiptap-container"),
- content: component.element,
- showOnCreate: true,
- interactive: true,
- trigger: "manual",
- placement: "bottom-start",
- });
- },
- onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
- component?.updateProps(props);
-
- popup &&
- popup[0].setProps({
- getReferenceClientRect: props.clientRect,
- });
- },
- onKeyDown: (props: { event: KeyboardEvent }) => {
- if (props.event.key === "Escape") {
- popup?.[0].hide();
-
- return true;
- }
-
- // @ts-ignore
- return component?.ref?.onKeyDown(props);
- },
- onExit: () => {
- popup?.[0].destroy();
- component?.destroy();
- },
- };
-};
-
-export const SlashCommand = (
- uploadFile: UploadImage,
- setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
-) =>
- Command.configure({
- suggestion: {
- items: getSuggestionItems(uploadFile, setIsSubmitting),
- render: renderItems,
- },
- });
-
-export default SlashCommand;
diff --git a/packages/editor/rich-text-editor/src/table/table-cell.ts b/packages/editor/rich-text-editor/src/table/table-cell.ts
deleted file mode 100644
index 643cb8c64..000000000
--- a/packages/editor/rich-text-editor/src/table/table-cell.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { TableCell } from "@tiptap/extension-table-cell";
-
-export const CustomTableCell = TableCell.extend({
- addAttributes() {
- return {
- ...this.parent?.(),
- isHeader: {
- default: false,
- parseHTML: (element) => {
- isHeader: element.tagName === "TD";
- },
- renderHTML: (attributes) => {
- tag: attributes.isHeader ? "th" : "td";
- },
- },
- };
- },
- renderHTML({ HTMLAttributes }) {
- if (HTMLAttributes.isHeader) {
- return [
- "th",
- {
- ...HTMLAttributes,
- class: `relative ${HTMLAttributes.class}`,
- },
- ["span", { class: "absolute top-0 right-0" }],
- 0,
- ];
- }
- return ["td", HTMLAttributes, 0];
- },
-});
diff --git a/packages/editor/rich-text-editor/src/table/table-header.ts b/packages/editor/rich-text-editor/src/table/table-header.ts
deleted file mode 100644
index f23aa93ef..000000000
--- a/packages/editor/rich-text-editor/src/table/table-header.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { TableHeader as BaseTableHeader } from "@tiptap/extension-table-header";
-
-const TableHeader = BaseTableHeader.extend({
- content: "paragraph",
-});
-
-export { TableHeader };
diff --git a/packages/editor/rich-text-editor/src/table/table.ts b/packages/editor/rich-text-editor/src/table/table.ts
deleted file mode 100644
index 9b727bb51..000000000
--- a/packages/editor/rich-text-editor/src/table/table.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Table as BaseTable } from "@tiptap/extension-table";
-
-const Table = BaseTable.configure({
- resizable: true,
- cellMinWidth: 100,
- allowTableNodeSelection: true,
-});
-
-export { Table };
diff --git a/packages/editor/rich-text-editor/src/types/upload-image.ts b/packages/editor/rich-text-editor/src/types/upload-image.ts
deleted file mode 100644
index 3cf1408d2..000000000
--- a/packages/editor/rich-text-editor/src/types/upload-image.ts
+++ /dev/null
@@ -1 +0,0 @@
-export type UploadImage = (file: File) => Promise;
diff --git a/packages/editor/rich-text-editor/src/ui/extensions/index.tsx b/packages/editor/rich-text-editor/src/ui/extensions/index.tsx
new file mode 100644
index 000000000..7c394b53e
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/ui/extensions/index.tsx
@@ -0,0 +1,20 @@
+import HorizontalRule from "@tiptap/extension-horizontal-rule";
+import Placeholder from "@tiptap/extension-placeholder";
+import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
+import { lowlight } from "lowlight/lib/core";
+import { InputRule } from "@tiptap/core";
+
+import ts from "highlight.js/lib/languages/typescript";
+
+import "highlight.js/styles/github-dark.css";
+import SlashCommand from "./slash-command";
+import { UploadImage } from "..";
+
+lowlight.registerLanguage("ts", ts);
+
+export const RichTextEditorExtensions = (
+ uploadFile: UploadImage,
+ setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
+) => [
+ SlashCommand(uploadFile, setIsSubmitting),
+ ];
diff --git a/packages/editor/core/src/ui/extensions/slash-command.tsx b/packages/editor/rich-text-editor/src/ui/extensions/slash-command.tsx
similarity index 98%
rename from packages/editor/core/src/ui/extensions/slash-command.tsx
rename to packages/editor/rich-text-editor/src/ui/extensions/slash-command.tsx
index 844d4c55a..8fdb5ddcd 100644
--- a/packages/editor/core/src/ui/extensions/slash-command.tsx
+++ b/packages/editor/rich-text-editor/src/ui/extensions/slash-command.tsx
@@ -17,9 +17,8 @@ import {
ImageIcon,
Table,
} from "lucide-react";
-import { startImageUpload } from "@/ui/plugins/upload-image";
-import { cn } from "@/lib/utils";
-import { UploadImage } from "@/types/upload-image";
+import { UploadImage } from "..";
+import { cn, startImageUpload } from "@plane/editor-core";
interface CommandItemProps {
title: string;
diff --git a/packages/editor/rich-text-editor/src/ui/index.tsx b/packages/editor/rich-text-editor/src/ui/index.tsx
new file mode 100644
index 000000000..8784ae17d
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/ui/index.tsx
@@ -0,0 +1,83 @@
+"use client"
+import * as React from 'react';
+import { EditorContainer, EditorContentWrapper, getEditorClassNames, useEditor } from '@plane/editor-core';
+import { EditorBubbleMenu } from './menus/bubble-menu';
+import { RichTextEditorExtensions } from './extensions';
+
+export type UploadImage = (file: File) => Promise;
+export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise;
+
+interface ITiptapEditor {
+ value: string;
+ uploadFile: UploadImage;
+ deleteFile: DeleteImage;
+ noBorder?: boolean;
+ borderOnFocus?: boolean;
+ customClassName?: string;
+ editorContentCustomClassNames?: string;
+ onChange?: (json: any, html: string) => void;
+ setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
+ setShouldShowAlert?: (showAlert: boolean) => void;
+ editable?: boolean;
+ forwardedRef?: any;
+ debouncedUpdatesEnabled?: boolean;
+}
+
+interface TiptapProps extends ITiptapEditor {
+ forwardedRef?: React.Ref;
+}
+
+interface EditorHandle {
+ clearEditor: () => void;
+ setEditorValue: (content: string) => void;
+}
+
+const RichTextEditor = ({
+ onChange,
+ debouncedUpdatesEnabled,
+ editable,
+ setIsSubmitting,
+ setShouldShowAlert,
+ editorContentCustomClassNames,
+ value,
+ uploadFile,
+ deleteFile,
+ noBorder,
+ borderOnFocus,
+ customClassName,
+ forwardedRef,
+}: TiptapProps) => {
+ const editor = useEditor({
+ onChange,
+ debouncedUpdatesEnabled,
+ editable,
+ setIsSubmitting,
+ setShouldShowAlert,
+ value,
+ uploadFile,
+ deleteFile,
+ forwardedRef,
+ extensions: RichTextEditorExtensions(uploadFile, setIsSubmitting)
+ });
+
+ const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
+
+ if (!editor) return null;
+
+ return (
+
+ {editor && }
+
+
+
+
+ );
+};
+
+const RichTextEditorWithRef = React.forwardRef((props, ref) => (
+
+));
+
+RichTextEditorWithRef.displayName = "RichTextEditorWithRef";
+
+export { RichTextEditor, RichTextEditorWithRef};
diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx
new file mode 100644
index 000000000..b9ce6159d
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx
@@ -0,0 +1,121 @@
+import { BubbleMenu, BubbleMenuProps } from "@tiptap/react";
+import { FC, useState } from "react";
+import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, CodeIcon } from "lucide-react";
+
+import { NodeSelector } from "./node-selector";
+import { LinkSelector } from "./link-selector";
+import { cn } from "@plane/editor-core";
+
+export interface BubbleMenuItem {
+ name: string;
+ isActive: () => boolean;
+ command: () => void;
+ icon: typeof BoldIcon;
+}
+
+type EditorBubbleMenuProps = Omit;
+
+export const EditorBubbleMenu: FC = (props: any) => {
+ const items: BubbleMenuItem[] = [
+ {
+ name: "bold",
+ isActive: () => props.editor?.isActive("bold"),
+ command: () => props.editor?.chain().focus().toggleBold().run(),
+ icon: BoldIcon,
+ },
+ {
+ name: "italic",
+ isActive: () => props.editor?.isActive("italic"),
+ command: () => props.editor?.chain().focus().toggleItalic().run(),
+ icon: ItalicIcon,
+ },
+ {
+ name: "underline",
+ isActive: () => props.editor?.isActive("underline"),
+ command: () => props.editor?.chain().focus().toggleUnderline().run(),
+ icon: UnderlineIcon,
+ },
+ {
+ name: "strike",
+ isActive: () => props.editor?.isActive("strike"),
+ command: () => props.editor?.chain().focus().toggleStrike().run(),
+ icon: StrikethroughIcon,
+ },
+ {
+ name: "code",
+ isActive: () => props.editor?.isActive("code"),
+ command: () => props.editor?.chain().focus().toggleCode().run(),
+ icon: CodeIcon,
+ },
+ ];
+
+ const bubbleMenuProps: EditorBubbleMenuProps = {
+ ...props,
+ shouldShow: ({ editor }) => {
+ if (!editor.isEditable) {
+ return false;
+ }
+ if (editor.isActive("image")) {
+ return false;
+ }
+ return editor.view.state.selection.content().size > 0;
+ },
+ tippyOptions: {
+ moveTransition: "transform 0.15s ease-out",
+ onHidden: () => {
+ setIsNodeSelectorOpen(false);
+ setIsLinkSelectorOpen(false);
+ },
+ },
+ };
+
+ const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
+ const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false);
+
+ return (
+
+ {!props.editor.isActive("table") && (
+ {
+ setIsNodeSelectorOpen(!isNodeSelectorOpen);
+ setIsLinkSelectorOpen(false);
+ }}
+ />
+ )}
+ {
+ setIsLinkSelectorOpen(!isLinkSelectorOpen);
+ setIsNodeSelectorOpen(false);
+ }}
+ />
+
+ {items.map((item, index) => (
+
+ ))}
+
+
+ );
+};
diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/link-selector.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/link-selector.tsx
new file mode 100644
index 000000000..cf347cf3b
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/link-selector.tsx
@@ -0,0 +1,93 @@
+import { Editor } from "@tiptap/core";
+import { Check, Trash } from "lucide-react";
+import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from "react";
+import isValidHttpUrl from "@/ui/menus/bubble-menu/utils";
+import { cn } from "@plane/editor-core";
+
+interface LinkSelectorProps {
+ editor: Editor;
+ isOpen: boolean;
+ setIsOpen: Dispatch>;
+}
+
+export const LinkSelector: FC = ({ editor, isOpen, setIsOpen }) => {
+ const inputRef = useRef(null);
+
+ const onLinkSubmit = useCallback(() => {
+ const input = inputRef.current;
+ const url = input?.value;
+ if (url && isValidHttpUrl(url)) {
+ editor.chain().focus().setLink({ href: url }).run();
+ setIsOpen(false);
+ }
+ }, [editor, inputRef, setIsOpen]);
+
+ useEffect(() => {
+ inputRef.current && inputRef.current?.focus();
+ });
+
+ return (
+
+
+ {isOpen && (
+
{
+ if (e.key === "Enter") {
+ e.preventDefault();
+ onLinkSubmit();
+ }
+ }}
+ >
+
+ {editor.getAttributes("link").href ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ );
+};
diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx
new file mode 100644
index 000000000..e1f8ce211
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx
@@ -0,0 +1,130 @@
+import { cn } from "@plane/editor-core";
+import { Editor } from "@tiptap/core";
+import {
+ Check,
+ ChevronDown,
+ Heading1,
+ Heading2,
+ Heading3,
+ TextQuote,
+ ListOrdered,
+ TextIcon,
+ Code,
+ CheckSquare,
+} from "lucide-react";
+import { Dispatch, FC, SetStateAction } from "react";
+
+import { BubbleMenuItem } from ".";
+
+interface NodeSelectorProps {
+ editor: Editor;
+ isOpen: boolean;
+ setIsOpen: Dispatch>;
+}
+
+export const NodeSelector: FC = ({ editor, isOpen, setIsOpen }) => {
+ const items: BubbleMenuItem[] = [
+ {
+ name: "Text",
+ icon: TextIcon,
+ command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(),
+ isActive: () =>
+ editor.isActive("paragraph") &&
+ !editor.isActive("bulletList") &&
+ !editor.isActive("orderedList"),
+ },
+ {
+ name: "H1",
+ icon: Heading1,
+ command: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
+ isActive: () => editor.isActive("heading", { level: 1 }),
+ },
+ {
+ name: "H2",
+ icon: Heading2,
+ command: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
+ isActive: () => editor.isActive("heading", { level: 2 }),
+ },
+ {
+ name: "H3",
+ icon: Heading3,
+ command: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
+ isActive: () => editor.isActive("heading", { level: 3 }),
+ },
+ {
+ name: "To-do List",
+ icon: CheckSquare,
+ command: () => editor.chain().focus().toggleTaskList().run(),
+ isActive: () => editor.isActive("taskItem"),
+ },
+ {
+ name: "Bullet List",
+ icon: ListOrdered,
+ command: () => editor.chain().focus().toggleBulletList().run(),
+ isActive: () => editor.isActive("bulletList"),
+ },
+ {
+ name: "Numbered List",
+ icon: ListOrdered,
+ command: () => editor.chain().focus().toggleOrderedList().run(),
+ isActive: () => editor.isActive("orderedList"),
+ },
+ {
+ name: "Quote",
+ icon: TextQuote,
+ command: () =>
+ editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
+ isActive: () => editor.isActive("blockquote"),
+ },
+ {
+ name: "Code",
+ icon: Code,
+ command: () => editor.chain().focus().toggleCodeBlock().run(),
+ isActive: () => editor.isActive("codeBlock"),
+ },
+ ];
+
+ const activeItem = items.filter((item) => item.isActive()).pop() ?? {
+ name: "Multiple",
+ };
+
+ return (
+
+
+
+ {isOpen && (
+
+ {items.map((item, index) => (
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/utils/index.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/utils/index.tsx
new file mode 100644
index 000000000..b5add3f54
--- /dev/null
+++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/utils/index.tsx
@@ -0,0 +1,11 @@
+export default function isValidHttpUrl(string: string): boolean {
+ let url: URL;
+
+ try {
+ url = new URL(string);
+ } catch (_) {
+ return false;
+ }
+
+ return url.protocol === "http:" || url.protocol === "https:";
+}
diff --git a/space/components/issues/peek-overview/comment/add-comment.tsx b/space/components/issues/peek-overview/comment/add-comment.tsx
index 7869667d2..c06634fc6 100644
--- a/space/components/issues/peek-overview/comment/add-comment.tsx
+++ b/space/components/issues/peek-overview/comment/add-comment.tsx
@@ -11,7 +11,7 @@ import { SecondaryButton } from "components/ui";
// types
import { Comment } from "types/issue";
// components
-import { TiptapEditorWithRef } from "@plane/editor";
+import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// service
import fileService from "@/services/file.service";
@@ -71,10 +71,9 @@ export const AddComment: React.FC = observer((props) => {
name="comment_html"
control={control}
render={({ field: { value, onChange } }) => (
- = observer((props) => {
control={control}
name="comment_html"
render={({ field: { onChange, value } }) => (
- = observer((props) => {
-
= ({ issueDetails }) => {
{issueDetails.name}
{issueDetails.description_html !== "" && issueDetails.description_html !== "" && (
- {
- return this.mediaUpload(`/api/workspaces/${workspaceSlug}/file-assets/`, file)
+ return this.post(`/api/workspaces/${workspaceSlug}/file-assets/`, file, {
+ headers: {
+ ...this.getHeaders(),
+ "Content-Type": "multipart/form-data",
+ },
+ })
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
+ getUploadFileFunction(workspaceSlug: string): (file: File) => Promise {
+ return async (file: File) => {
+ const formData = new FormData();
+ formData.append("asset", file);
+ formData.append("attributes", JSON.stringify({}));
+
+ const data = await this.uploadFile(workspaceSlug, formData);
+ return data.asset;
+ };
+ }
+
async deleteImage(assetUrlWithWorkspaceId: string): Promise {
return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`)
.then((response) => response?.status)
diff --git a/turbo.json b/turbo.json
index e7a4a08d5..40f67bc05 100644
--- a/turbo.json
+++ b/turbo.json
@@ -44,7 +44,7 @@
"cache": false,
"persistent": true,
"dependsOn": [
- "@plane/editor#build"
+ "@plane/editor-core#build"
]
},
"test": {
diff --git a/web/components/core/modals/gpt-assistant-modal.tsx b/web/components/core/modals/gpt-assistant-modal.tsx
index 8c5895f39..236037531 100644
--- a/web/components/core/modals/gpt-assistant-modal.tsx
+++ b/web/components/core/modals/gpt-assistant-modal.tsx
@@ -11,7 +11,7 @@ import useUserAuth from "hooks/use-user-auth";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
// components
-import { TiptapEditor, TiptapEditorWithRef } from "@plane/editor";
+import { RichTextEditor, RichTextEditorWithRef } from "@plane/rich-text-editor";
// types
import { IIssue, IPageBlock } from "types";
// services
@@ -143,10 +143,9 @@ export const GptAssistantModal: React.FC = ({
{((content && content !== "") || (htmlContent && htmlContent !== "")) && (
Content:
-
${content}`}
customClassName="-m-3"
noBorder
@@ -159,10 +158,9 @@ export const GptAssistantModal: React.FC = ({
{response !== "" && (
Response:
-
${response}`}
customClassName="-mx-3 -my-3"
noBorder
diff --git a/web/components/issues/comment/add-comment.tsx b/web/components/issues/comment/add-comment.tsx
index 76a45faa4..4b0e92ba7 100644
--- a/web/components/issues/comment/add-comment.tsx
+++ b/web/components/issues/comment/add-comment.tsx
@@ -3,9 +3,9 @@ import { useRouter } from "next/router";
// react-hook-form
import { useForm, Controller } from "react-hook-form";
// components
-import { TiptapEditorWithRef } from "@plane/editor";
+import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
// ui
-import { Icon, SecondaryButton, Tooltip } from "components/ui";
+import { SecondaryButton } from "components/ui";
// types
import type { IIssueComment } from "types";
// services
@@ -74,23 +74,20 @@ export const AddComment: React.FC = ({
(
+ render={({ field: { onChange: onAccessChange, value: accessValue } }) => (
(
- " : commentValue}
customClassName="p-3 min-h-[100px] shadow-sm"
debouncedUpdatesEnabled={false}
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
- accessValue={value}
- onAccessChange={onChange}
- commentAccess={commentAccess}
+ commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }}
/>
)}
/>
diff --git a/web/components/issues/comment/comment-card.tsx b/web/components/issues/comment/comment-card.tsx
index 19cc0fe7e..147c49bd1 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 { TiptapEditorWithRef } from "@plane/editor";
+import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
// helpers
import { timeAgo } from "helpers/date-time.helper";
// types
@@ -112,10 +112,9 @@ export const CommentCard: React.FC = ({
onSubmit={handleSubmit(onEnter)}
>
- = ({
/>
)}
- = ({
if (!value) return <>>;
return (
- = (props) => {
return (
= (props) => {
return (
= ({
if (!data)
return (
"}
debouncedUpdatesEnabled={false}
@@ -304,9 +303,8 @@ export const CreateUpdateBlockInline: React.FC = ({
return (
0
diff --git a/web/components/pages/single-page-block.tsx b/web/components/pages/single-page-block.tsx
index 592da42c7..356186882 100644
--- a/web/components/pages/single-page-block.tsx
+++ b/web/components/pages/single-page-block.tsx
@@ -19,7 +19,7 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
import { GptAssistantModal } from "components/core";
import { CreateUpdateBlockInline } from "components/pages";
-import { TiptapEditor } from "@plane/editor";
+import { RichTextEditor } from "@plane/rich-text-editor";
// ui
import { CustomMenu, TextArea } from "components/ui";
// icons
@@ -452,10 +452,9 @@ export const SinglePageBlock: React.FC = ({
{showBlockDetails
? block.description_html.length > 7 && (
- = ({ disabled = false, onSubmit }) => {
control={control}
render={({ field: { value, onChange } }) => (
" : value}
customClassName="p-3 min-h-[100px] shadow-sm"
diff --git a/web/components/web-view/issue-web-view-form.tsx b/web/components/web-view/issue-web-view-form.tsx
index 138242104..8a791e9ac 100644
--- a/web/components/web-view/issue-web-view-form.tsx
+++ b/web/components/web-view/issue-web-view-form.tsx
@@ -16,7 +16,7 @@ import useReloadConfirmations from "hooks/use-reload-confirmation";
import { TextArea } from "components/ui";
// components
-import { TiptapEditor } from "@plane/editor";
+import { RichTextEditor } from "@plane/rich-text-editor";
import { Label } from "components/web-view";
// types
@@ -123,8 +123,8 @@ export const IssueWebViewForm: React.FC = (props) => {
if (!value) return <>>;
return (
- = (props) => {
? ""
: value
}
- workspaceSlug={workspaceSlug!.toString()}
debouncedUpdatesEnabled={true}
setShouldShowAlert={setShowAlert}
setIsSubmitting={setIsSubmitting}
diff --git a/web/package.json b/web/package.json
index 8e4629242..23aa28bfa 100644
--- a/web/package.json
+++ b/web/package.json
@@ -27,7 +27,8 @@
"@nivo/pie": "0.80.0",
"@nivo/scatterplot": "0.80.0",
"@sentry/nextjs": "^7.36.0",
- "@plane/editor": "*",
+ "@plane/lite-text-editor": "*",
+ "@plane/rich-text-editor": "*",
"@types/lodash.debounce": "^4.0.7",
"@types/react-datepicker": "^4.8.0",
"axios": "^1.1.3",
diff --git a/web/pages/[workspaceSlug]/editor.tsx b/web/pages/[workspaceSlug]/editor.tsx
index d825826c2..7b16bdd54 100644
--- a/web/pages/[workspaceSlug]/editor.tsx
+++ b/web/pages/[workspaceSlug]/editor.tsx
@@ -1,4 +1,4 @@
-import { TiptapEditor } from "@plane/editor";
+import { RichTextEditor } from "@plane/rich-text-editor";
import type { NextPage } from "next";
import { useCallback, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
@@ -135,8 +135,8 @@ const Editor: NextPage = () => {
name="description_html"
control={control}
render={({ field: { value, onChange } }) => (
- {
}
editable={editable === "true"}
noBorder={true}
- workspaceSlug={cookies.MOBILE_slug ?? ""}
debouncedUpdatesEnabled={true}
setShouldShowAlert={setShowAlert}
setIsSubmitting={setIsSubmitting}
diff --git a/web/pages/[workspaceSlug]/me/profile/activity.tsx b/web/pages/[workspaceSlug]/me/profile/activity.tsx
index b6c478a86..68e46723d 100644
--- a/web/pages/[workspaceSlug]/me/profile/activity.tsx
+++ b/web/pages/[workspaceSlug]/me/profile/activity.tsx
@@ -9,7 +9,7 @@ import userService from "services/user.service";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import { ActivityIcon, ActivityMessage } from "components/core";
-import { TiptapEditor } from "@plane/editor";
+import { RichTextEditor } from "@plane/rich-text-editor";
// icons
import { ArrowTopRightOnSquareIcon, ChatBubbleLeftEllipsisIcon } from "@heroicons/react/24/outline";
// ui
@@ -98,10 +98,9 @@ const ProfileActivity = () => {
- {
name="data_html"
control={control}
render={({ field: { value, onChange } }) => (
- {
}
editable={isEditable}
noBorder={true}
- workspaceSlug={workspaceSlug?.toString() ?? ""}
debouncedUpdatesEnabled={true}
customClassName="min-h-[150px] shadow-sm"
editorContentCustomClassNames="pb-9"
diff --git a/yarn.lock b/yarn.lock
index b2d3e099b..bdee87350 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2808,7 +2808,7 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@18.0.15", "@types/react@18.0.28", "@types/react@18.2.0", "@types/react@^18.0.17":
+"@types/react@*", "@types/react@18.0.15", "@types/react@18.0.28", "@types/react@18.2.0", "@types/react@^18.0.17", "@types/react@^18.2.5":
version "18.2.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21"
integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==