refactoring tiptap to core/lite/rich text editor

This commit is contained in:
Palanikannan1437 2023-10-02 14:52:16 +05:30
parent b5228bb0ef
commit 3d87a56e3b
17 changed files with 122 additions and 91 deletions

View File

@ -155,7 +155,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
}
}
#tiptap-container {
#editor-container {
table {
border-collapse: collapse;
table-layout: fixed;

View File

@ -9,11 +9,11 @@ interface EditorContainerProps {
export const EditorContainer = ({ editor, editorClassNames, children }: EditorContainerProps) => (
<div
id="tiptap-container"
id="editor-container"
onClick={() => {
editor?.chain().focus().run();
}}
className={`tiptap-editor-container cursor-text ${editorClassNames}`}
className={`cursor-text ${editorClassNames}`}
>
{children}
</div>

View File

@ -19,7 +19,7 @@ import { DeleteImage } from "@/types/delete-image";
import isValidHttpUrl from "@/ui/menus/bubble-menu/utils"
export const TiptapExtensions = (
export const CoreEditorExtensions = (
deleteFile: DeleteImage,
) => [
StarterKit.configure({

View File

@ -1,10 +1,10 @@
import { useEditor as useCustomEditor, Editor, Extension, Node, Mark } from "@tiptap/react";
import { useImperativeHandle, useRef, MutableRefObject, forwardRef } from "react";
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
import { useImperativeHandle, useRef, MutableRefObject } 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 { CoreEditorProps } from "../props";
import { CoreEditorExtensions } from "../extensions";
import { EditorProps } from '@tiptap/pm/view';
const DEBOUNCE_DELAY = 1500;
@ -27,10 +27,10 @@ export const useEditor = ({ uploadFile, editable, deleteFile, editorProps = {},
const editor = useCustomEditor({
editable: editable ?? true,
editorProps: {
...TiptapEditorProps(uploadFile, setIsSubmitting),
...CoreEditorProps(uploadFile, setIsSubmitting),
...editorProps,
},
extensions: [...TiptapExtensions(deleteFile), ...extensions],
extensions: [...CoreEditorExtensions(deleteFile), ...extensions],
content: (typeof value === "string" && value.trim() !== "") ? value : "<p></p>",
onUpdate: async ({ editor }) => {
// for instant feedback loop

View File

@ -9,7 +9,7 @@ import { useEditor } from './hooks/useEditor';
import { EditorContainer } from '@/ui/editor-container';
import { EditorContentWrapper } from '@/ui/editor-content';
interface ITiptapEditor {
interface ICoreEditor {
value: string;
uploadFile: UploadImage;
deleteFile: DeleteImage;
@ -34,7 +34,7 @@ interface ITiptapEditor {
editorProps?: EditorProps;
}
interface TiptapProps extends ITiptapEditor {
interface EditorCoreProps extends ICoreEditor {
forwardedRef?: React.Ref<EditorHandle>;
}
@ -43,7 +43,7 @@ interface EditorHandle {
setEditorValue: (content: string) => void;
}
const TiptapEditor = ({
const CoreEditor = ({
onChange,
debouncedUpdatesEnabled,
editable,
@ -57,7 +57,7 @@ const TiptapEditor = ({
borderOnFocus,
customClassName,
forwardedRef,
}: TiptapProps) => {
}: EditorCoreProps) => {
const editor = useEditor({
onChange,
debouncedUpdatesEnabled,
@ -83,10 +83,10 @@ const TiptapEditor = ({
);
};
const TiptapEditorWithRef = React.forwardRef<EditorHandle, ITiptapEditor>((props, ref) => (
<TiptapEditor {...props} forwardedRef={ref} />
const CoreEditorWithRef = React.forwardRef<EditorHandle, ICoreEditor>((props, ref) => (
<CoreEditor {...props} forwardedRef={ref} />
));
TiptapEditorWithRef.displayName = "TiptapEditorWithRef";
CoreEditorWithRef.displayName = "CoreEditorWithRef";
export { TiptapEditor, TiptapEditorWithRef };
export { CoreEditor, CoreEditorWithRef };

View File

@ -3,7 +3,7 @@ import { findTableAncestor } from "@/lib/utils";
import { startImageUpload } from "@/ui/plugins/upload-image";
import { UploadImage } from "@/types/upload-image";
export function TiptapEditorProps(
export function CoreEditorProps(
uploadFile: UploadImage,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
): EditorProps {

View File

@ -6,7 +6,7 @@ import { FixedMenu } from './menus/fixed-menu';
export type UploadImage = (file: File) => Promise<string>;
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
interface ITiptapEditor {
interface ILiteTextEditor {
value: string;
uploadFile: UploadImage;
deleteFile: DeleteImage;
@ -32,7 +32,7 @@ interface ITiptapEditor {
}
}
interface TiptapProps extends ITiptapEditor {
interface LiteTextEditorProps extends ILiteTextEditor {
forwardedRef?: React.Ref<EditorHandle>;
}
@ -56,7 +56,7 @@ const LiteTextEditor = ({
customClassName,
forwardedRef,
commentAccessSpecifier,
}: TiptapProps) => {
}: LiteTextEditorProps) => {
const editor = useEditor({
onChange,
debouncedUpdatesEnabled,
@ -87,7 +87,7 @@ const LiteTextEditor = ({
);
};
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ITiptapEditor>((props, ref) => (
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>((props, ref) => (
<LiteTextEditor {...props} forwardedRef={ref} />
));

View File

@ -1,3 +1,3 @@
import "./styles/github-dark.css";
import "@/styles/github-dark.css";
export { RichTextEditor, RichTextEditorWithRef } from "@/ui";

View File

@ -315,7 +315,7 @@ const renderItems = () => {
// @ts-ignore
popup = tippy("body", {
getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector("#tiptap-container"),
appendTo: () => document.querySelector("#editor-container"),
content: component.element,
showOnCreate: true,
interactive: true,

View File

@ -7,7 +7,7 @@ import { RichTextEditorExtensions } from './extensions';
export type UploadImage = (file: File) => Promise<string>;
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
interface ITiptapEditor {
interface IRichTextEditor {
value: string;
uploadFile: UploadImage;
deleteFile: DeleteImage;
@ -23,7 +23,7 @@ interface ITiptapEditor {
debouncedUpdatesEnabled?: boolean;
}
interface TiptapProps extends ITiptapEditor {
interface RichTextEditorProps extends IRichTextEditor {
forwardedRef?: React.Ref<EditorHandle>;
}
@ -46,7 +46,7 @@ const RichTextEditor = ({
borderOnFocus,
customClassName,
forwardedRef,
}: TiptapProps) => {
}: RichTextEditorProps) => {
const editor = useEditor({
onChange,
debouncedUpdatesEnabled,
@ -74,7 +74,7 @@ const RichTextEditor = ({
);
};
const RichTextEditorWithRef = React.forwardRef<EditorHandle, ITiptapEditor>((props, ref) => (
const RichTextEditorWithRef = React.forwardRef<EditorHandle, IRichTextEditor>((props, ref) => (
<RichTextEditor {...props} forwardedRef={ref} />
));

View File

@ -155,7 +155,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
}
}
#tiptap-container {
#editor-container {
table {
border-collapse: collapse;
table-layout: fixed;

View File

@ -25,7 +25,7 @@ import { CreateLabelModal } from "components/labels";
// ui
import { CustomMenu, Input, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
// components
import { TiptapEditorWithRef } from "@plane/rich-text-editor";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// icons
import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline";
// types
@ -386,7 +386,7 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
if (!value && !watch("description_html")) return <></>;
return (
<TiptapEditorWithRef
<RichTextEditorWithRef
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
ref={editorRef}

View File

@ -25,7 +25,7 @@ import { CreateLabelModal } from "components/labels";
// ui
import { CustomMenu, Input, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
// components
import { TiptapEditorWithRef } from "@plane/rich-text-editor";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// icons
import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline";
@ -353,8 +353,7 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
{issueName && issueName !== "" && (
<button
type="button"
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${
iAmFeelingLucky ? "cursor-wait" : ""
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${iAmFeelingLucky ? "cursor-wait" : ""
}`}
onClick={handleAutoGenerateDescription}
disabled={iAmFeelingLucky}
@ -384,7 +383,7 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
if (!value && !watch("description_html")) return <></>;
return (
<TiptapEditorWithRef
<RichTextEditorWithRef
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
ref={editorRef}

View File

@ -11,7 +11,7 @@ import aiService from "services/ai.service";
import useToast from "hooks/use-toast";
// components
import { GptAssistantModal } from "components/core";
import { TiptapEditorWithRef } from "@plane/rich-text-editor";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
import { PrimaryButton, SecondaryButton, TextArea } from "components/ui";
// types
import { ICurrentUserResponse, IPageBlock } from "types";
@ -281,7 +281,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
render={({ field: { value, onChange } }) => {
if (!data)
return (
<TiptapEditorWithRef
<RichTextEditorWithRef
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
ref={editorRef}
@ -302,7 +302,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
);
return (
<TiptapEditorWithRef
<RichTextEditorWithRef
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
ref={editorRef}

View File

@ -10,8 +10,7 @@ import { useForm, Controller } from "react-hook-form";
import useProjectDetails from "hooks/use-project-details";
// components
import { TiptapEditorWithRef } from "@plane/rich-text-editor";
import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
// icons
import { Send } from "lucide-react";
@ -32,7 +31,13 @@ type Props = {
onSubmit: (data: IIssueComment) => Promise<void>;
};
const commentAccess = [
type commentAccessType = {
icon: string;
key: string;
label: "Private" | "Public";
}
const commentAccess: commentAccessType[] = [
{
icon: "lock",
key: "INTERNAL",
@ -53,7 +58,7 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit }) => {
const { projectDetails } = useProjectDetails();
const showAccessSpecifier = projectDetails?.is_deployed;
const showAccessSpecifier = projectDetails?.is_deployed || false;
const {
control,
@ -74,50 +79,29 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit }) => {
return (
<form className="w-full flex gap-x-2" onSubmit={handleSubmit(handleAddComment)}>
<div className="relative flex-grow">
<Controller
name="access"
control={control}
render={({ field: { onChange: onAccessChange, value: accessValue } }) => (
<Controller
name="comment_html"
control={control}
render={({ field: { value, onChange } }) => (
<TiptapEditorWithRef
render={({ field: { onChange: onCommentChange, value: commentValue } }) => (
<LiteTextEditorWithRef
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
ref={editorRef}
value={!value || value === "" ? "<p></p>" : value}
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
customClassName="p-3 min-h-[100px] shadow-sm"
debouncedUpdatesEnabled={false}
onChange={(comment_json: Object, comment_html: string) => onChange(comment_html)}
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }}
/>
)}
/>
{showAccessSpecifier && (
<div className="relative bottom-2 left-3 z-[1]">
<Controller
control={control}
name="access"
render={({ field: { onChange, value } }) => (
<div className="flex border border-custom-border-300 divide-x divide-custom-border-300 rounded overflow-hidden">
{commentAccess.map((access) => (
<Tooltip key={access.key} tooltipContent={access.label}>
<button
type="button"
onClick={() => onChange(access.key)}
className={`grid place-items-center p-1 hover:bg-custom-background-80 ${value === access.key ? "bg-custom-background-80" : ""
}`}
>
<Icon
iconName={access.icon}
className={`w-4 h-4 -mt-1 ${value === access.key ? "!text-custom-text-100" : "!text-custom-text-400"
}`}
/>
</button>
</Tooltip>
))}
</div>
)}
/>
</div>
)}
</div>
<div className="inline">
<PrimaryButton

View File

@ -155,7 +155,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
}
}
#tiptap-container {
#editor-container {
table {
border-collapse: collapse;
table-layout: fixed;

View File

@ -2355,6 +2355,11 @@
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.10.tgz#6d8f3c777f1700dcc6c903b1185576754175e366"
integrity sha512-yhUKsac6nlqbPQfwQnp+4Jb110EqmzocXKoZacLwzHpM7JVsr2+LXMDu9kahtrvHNJErJljhnQvDHRsrrYeJkQ==
"@tiptap/core@^2.1.11":
version "2.1.11"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.11.tgz#06bbd189c6b2dffe58b1c80f848737d76fb012bd"
integrity sha512-1W2DdjpPwfphHgQ3Qm4s5wzCnEjiXm1TeZ+6/zBl89yKURXgv8Mw1JGdj/NcImQjtDcsNn97MscACK3GKbEJBA==
"@tiptap/extension-blockquote@^2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.1.10.tgz#dc475bef70dd460fc730a14b3b4cc18f37cd1b2d"
@ -2382,6 +2387,11 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.1.10.tgz#22dabaa8c087bd03c160590f7b8bf9b1501752b5"
integrity sha512-HBrsgDX1sMx6FSoKxAhz2On8lwL8S1lqNryMQBTE63PemjOxcyxPNdGWZz+JfQmxyvymQoGhibaW5ImNAK84Zg==
"@tiptap/extension-code-block-lowlight@^2.1.11":
version "2.1.11"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.1.11.tgz#6eec38c3b8662fae81ec2f117a2d18564f1fbb1a"
integrity sha512-k3olDvsRYO32JR9hyNa6VLqUdhwcpLwvR4Z6tJ66jHag5rsfP/7JZxJhrX9A1AF/jRCILdTiq9DTKybHieFjsw==
"@tiptap/extension-code-block@^2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.1.10.tgz#a125a12f716728b271a130178c6fc60237ed46f5"
@ -2444,6 +2454,11 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.10.tgz#cfdb67530be100054fc8511942d4ec3534acf828"
integrity sha512-91lGpK2d6WMPhrMDPBURS8z8pEg1CUBYy7GmBenKvvgh+JzVhG+U6MtykfWNfm2R4iRXOl1xLbyUOCiOSUXodQ==
"@tiptap/extension-horizontal-rule@^2.1.11":
version "2.1.11"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.11.tgz#e423a2b41123ef7f8d778a1cd026e6606e7be28b"
integrity sha512-uvHPa2YCKnDhtSBSZB3lk5U4H3wRKP0DNvVx4Y2F7MdQianVzcyOd1pZYO9BQs+lUB1aZots6doE69Zqz3mU2Q==
"@tiptap/extension-image@^2.1.7":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.1.10.tgz#6c597ad02285f1f3508fd4aa21e30213657cbd7c"
@ -2481,6 +2496,11 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.0.3.tgz#69575353f09fc7524c9cdbfbf16c04f73c29d154"
integrity sha512-Z42jo0termRAf0S0L8oxrts94IWX5waU4isS2CUw8xCUigYyCFslkhQXkWATO1qRbjNFLKN2C9qvCgGf4UeBrw==
"@tiptap/extension-placeholder@^2.1.11":
version "2.1.11"
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.1.11.tgz#ba115f714dd48d5bbc65df277b74f357ff3b100e"
integrity sha512-laHYRFxJWj6m72Yf1v6Q5nF2nvwWpQlKUj6Yu/yluOOoVE92HpLqCAvA8RamqLtPiw5VxR3v3oCY0WNeQRvyIg==
"@tiptap/extension-strike@^2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.1.10.tgz#ec311395d16af15345b63d2dac2d459b9ad5fa9e"
@ -2630,6 +2650,13 @@
dependencies:
"@types/unist" "^2"
"@types/hast@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.1.tgz#e1705ec9258ac4885659c2d50bac06b4fcd16466"
integrity sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==
dependencies:
"@types/unist" "*"
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.2"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#dc1e9ded53375d37603c479cc12c693b0878aa2a"
@ -2851,6 +2878,11 @@
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==
"@types/unist@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.0.tgz#988ae8af1e5239e89f9fbb1ade4c935f4eeedf9a"
integrity sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==
"@types/unist@^2", "@types/unist@^2.0.0":
version "2.0.8"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c"
@ -3936,6 +3968,13 @@ detect-node-es@^1.1.0:
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
devlop@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018"
integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
dependencies:
dequal "^2.0.0"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
@ -5904,6 +5943,15 @@ lowlight@^2.9.0:
fault "^2.0.0"
highlight.js "~11.8.0"
lowlight@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-3.0.0.tgz#8772e6514f1c14cd576b5a7a22668f5aa2ddd10b"
integrity sha512-kedX6yxvgak8P4LGh3vKRDQuMbVcnP+qRuDJlve2w+mNJAbEhEQPjYCp9QJnpVL5F2aAAVjeIzzrbQZUKHiDJw==
dependencies:
"@types/hast" "^3.0.0"
devlop "^1.0.0"
highlight.js "~11.8.0"
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"