From dd5ff737d110c35270c38d6d80b315bd92340342 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Wed, 2 Aug 2023 23:00:47 +0530 Subject: [PATCH 01/27] remirror instances commented out to avoid prosemirror conflicts --- .../command-palette/command-pallette.tsx | 5 +- apps/app/components/core/feeds.tsx | 26 ++-- .../core/modals/gpt-assistant-modal.tsx | 48 +++---- .../components/issues/comment/add-comment.tsx | 60 ++++----- .../issues/comment/comment-card.tsx | 36 +++--- apps/app/components/issues/main-content.tsx | 63 +++++---- .../pages/create-update-block-inline.tsx | 122 +++++++++--------- apps/app/components/pages/page-form.tsx | 16 +-- .../components/pages/single-page-block.tsx | 40 +++--- apps/app/package.json | 11 +- 10 files changed, 214 insertions(+), 213 deletions(-) diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index 0b4c9577b..2a8a4aa4d 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -49,7 +49,7 @@ export const CommandPalette: React.FC = () => { workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null, workspaceSlug && projectId && issueId ? () => - issuesService.retrieve(workspaceSlug as string, projectId as string, issueId as string) + issuesService.retrieve(workspaceSlug as string, projectId as string, issueId as string) : null ); @@ -81,7 +81,8 @@ export const CommandPalette: React.FC = () => { if ( !(e.target instanceof HTMLTextAreaElement) && !(e.target instanceof HTMLInputElement) && - !(e.target as Element).classList?.contains("remirror-editor") + // !(e.target as Element).classList?.contains("remirror-editor") && + !(e.target as Element).closest(".tiptap-editor-container") ) { if ((ctrlKey || metaKey) && keyPressed === "k") { e.preventDefault(); diff --git a/apps/app/components/core/feeds.tsx b/apps/app/components/core/feeds.tsx index 2924ec456..8144bfc97 100644 --- a/apps/app/components/core/feeds.tsx +++ b/apps/app/components/core/feeds.tsx @@ -16,7 +16,7 @@ import { Icon } from "components/ui"; import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper"; // types -import RemirrorRichTextEditor from "components/rich-text-editor"; +// import RemirrorRichTextEditor from "components/rich-text-editor"; const activityDetails: { [key: string]: { @@ -250,18 +250,18 @@ export const Feeds: React.FC = ({ activities }) => { Commented {timeAgo(activity.created_at)}

-
- -
+ {/*
*/} + {/* */} + {/*
*/} diff --git a/apps/app/components/core/modals/gpt-assistant-modal.tsx b/apps/app/components/core/modals/gpt-assistant-modal.tsx index 7c05e036a..1d3af7a51 100644 --- a/apps/app/components/core/modals/gpt-assistant-modal.tsx +++ b/apps/app/components/core/modals/gpt-assistant-modal.tsx @@ -32,17 +32,17 @@ type FormData = { task: string; }; -const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { - ssr: false, -}); - -import { IRemirrorRichTextEditor } from "components/rich-text-editor"; - -const WrappedRemirrorRichTextEditor = forwardRef( - (props, ref) => -); - -WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { +// ssr: false, +// }); +// +// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; +// +// const WrappedRemirrorRichTextEditor = forwardRef( +// (props, ref) => +// ); +// +// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; export const GptAssistantModal: React.FC = ({ isOpen, @@ -150,19 +150,19 @@ export const GptAssistantModal: React.FC = ({ isOpen ? "block" : "hidden" }`} > - {((content && content !== "") || (htmlContent && htmlContent !== "

")) && ( -
- Content: - {content}

} - customClassName="-m-3" - noBorder - borderOnFocus={false} - editable={false} - ref={editorRef} - /> -
- )} + {/* {((content && content !== "") || (htmlContent && htmlContent !== "

")) && ( */} + {/*
*/} + {/* Content: */} + {/* {content}

} */} + {/* customClassName="-m-3" */} + {/* noBorder */} + {/* borderOnFocus={false} */} + {/* editable={false} */} + {/* ref={editorRef} */} + {/* /> */} + {/*
*/} + {/* )} */} {response !== "" && (
Response: diff --git a/apps/app/components/issues/comment/add-comment.tsx b/apps/app/components/issues/comment/add-comment.tsx index b7504d932..3b3cd21b0 100644 --- a/apps/app/components/issues/comment/add-comment.tsx +++ b/apps/app/components/issues/comment/add-comment.tsx @@ -18,23 +18,23 @@ import type { ICurrentUserResponse, IIssueComment } from "types"; // fetch-keys import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; -const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { - ssr: false, - loading: () => ( - - - - ), -}); -import { IRemirrorRichTextEditor } from "components/rich-text-editor"; - -const WrappedRemirrorRichTextEditor = React.forwardRef< - IRemirrorRichTextEditor, - IRemirrorRichTextEditor ->((props, ref) => ); - -WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; - +// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { +// ssr: false, +// loading: () => ( +// +// +// +// ), +// }); +// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; +// +// const WrappedRemirrorRichTextEditor = React.forwardRef< +// IRemirrorRichTextEditor, +// IRemirrorRichTextEditor +// >((props, ref) => ); +// +// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +// const defaultValues: Partial = { comment_json: "", comment_html: "", @@ -98,19 +98,19 @@ export const AddComment: React.FC = ({ issueId, user, disabled = false })
- ( - setValue("comment_json", jsonValue)} - onHTMLChange={(htmlValue) => setValue("comment_html", htmlValue)} - placeholder="Enter your comment..." - ref={editorRef} - /> - )} - /> + {/* ( */} + {/* setValue("comment_json", jsonValue)} */} + {/* onHTMLChange={(htmlValue) => setValue("comment_html", htmlValue)} */} + {/* placeholder="Enter your comment..." */} + {/* ref={editorRef} */} + {/* /> */} + {/* )} */} + {/* /> */} {isSubmitting ? "Adding..." : "Comment"} diff --git a/apps/app/components/issues/comment/comment-card.tsx b/apps/app/components/issues/comment/comment-card.tsx index 987254f3b..008290f43 100644 --- a/apps/app/components/issues/comment/comment-card.tsx +++ b/apps/app/components/issues/comment/comment-card.tsx @@ -16,17 +16,17 @@ import { timeAgo } from "helpers/date-time.helper"; // types import type { IIssueComment } from "types"; -const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false }); - -import { IRemirrorRichTextEditor } from "components/rich-text-editor"; - -const WrappedRemirrorRichTextEditor = React.forwardRef< - IRemirrorRichTextEditor, - IRemirrorRichTextEditor ->((props, ref) => ); - -WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; - +// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false }); +// +// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; +// +// const WrappedRemirrorRichTextEditor = React.forwardRef< +// IRemirrorRichTextEditor, +// IRemirrorRichTextEditor +// >((props, ref) => ); +// +// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +// type Props = { comment: IIssueComment; onSubmit: (comment: IIssueComment) => void; @@ -132,13 +132,13 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD
- + {/* */}
diff --git a/apps/app/components/issues/main-content.tsx b/apps/app/components/issues/main-content.tsx index 316d39e8a..7c29f0810 100644 --- a/apps/app/components/issues/main-content.tsx +++ b/apps/app/components/issues/main-content.tsx @@ -50,11 +50,11 @@ export const IssueMainContent: React.FC = ({ workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null, workspaceSlug && projectId && issueDetails?.parent ? () => - issuesService.subIssues( - workspaceSlug as string, - projectId as string, - issueDetails.parent ?? "" - ) + issuesService.subIssues( + workspaceSlug as string, + projectId as string, + issueDetails.parent ?? "" + ) : null ); const siblingIssuesList = siblingIssues?.sub_issues.filter((i) => i.id !== issueDetails.id); @@ -97,9 +97,8 @@ export const IssueMainContent: React.FC = ({ @@ -130,30 +129,30 @@ export const IssueMainContent: React.FC = ({ isAllowed={memberRole.isMember || memberRole.isOwner || !uneditable} /> - - -
- -
-
-
-

Attachments

-
- - -
-
-
-

Comments/Activity

- - + {/* */} + {/**/} + {/*
*/} + {/* */} + {/*
*/} + {/*
*/} + {/*
*/} + {/*

Attachments

*/} + {/*
*/} + {/* */} + {/* */} + {/*
*/} + {/*
*/} + {/*
*/} + {/*

Comments/Activity

*/} + {/* */} + {/* */}
); diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index 5e2dcb43c..3b73e041b 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -39,22 +39,22 @@ const defaultValues = { description_html: null, }; -const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { - ssr: false, - loading: () => ( - - - - ), -}); -import { IRemirrorRichTextEditor } from "components/rich-text-editor"; - -const WrappedRemirrorRichTextEditor = React.forwardRef< - IRemirrorRichTextEditor, - IRemirrorRichTextEditor ->((props, ref) => ); - -WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { +// ssr: false, +// loading: () => ( +// +// +// +// ), +// }); +// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; +// +// const WrappedRemirrorRichTextEditor = React.forwardRef< +// IRemirrorRichTextEditor, +// IRemirrorRichTextEditor +// >((props, ref) => ); +// +// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; export const CreateUpdateBlockInline: React.FC = ({ handleClose, @@ -296,51 +296,51 @@ export const CreateUpdateBlockInline: React.FC = ({ />
- { - if (!data) - return ( - setValue("description", jsonValue)} - onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - placeholder="Write something..." - customClassName="text-sm" - noBorder - borderOnFocus={false} - ref={editorRef} - /> - ); - else if (!value || !watch("description_html")) - return ( -
- ); - - return ( - 0 - ? value - : watch("description_html") && watch("description_html") !== "" - ? watch("description_html") - : { type: "doc", content: [{ type: "paragraph" }] } - } - onJSONChange={(jsonValue) => setValue("description", jsonValue)} - onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - placeholder="Write something..." - customClassName="text-sm" - noBorder - borderOnFocus={false} - ref={editorRef} - /> - ); - }} - /> + {/* { */} + {/* if (!data) */} + {/* return ( */} + {/* setValue("description", jsonValue)} */} + {/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */} + {/* placeholder="Write something..." */} + {/* customClassName="text-sm" */} + {/* noBorder */} + {/* borderOnFocus={false} */} + {/* ref={editorRef} */} + {/* /> */} + {/* ); */} + {/* else if (!value || !watch("description_html")) */} + {/* return ( */} + {/*
*/} + {/* ); */} + {/**/} + {/* return ( */} + {/* 0 */} + {/* ? value */} + {/* : watch("description_html") && watch("description_html") !== "" */} + {/* ? watch("description_html") */} + {/* : { type: "doc", content: [{ type: "paragraph" }] } */} + {/* } */} + {/* onJSONChange={(jsonValue) => setValue("description", jsonValue)} */} + {/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */} + {/* placeholder="Write something..." */} + {/* customClassName="text-sm" */} + {/* noBorder */} + {/* borderOnFocus={false} */} + {/* ref={editorRef} */} + {/* /> */} + {/* ); */} + {/* }} */} + {/* /> */}
- {showBlockDetails - ? block.description_html.length > 7 && ( - - ) - : block.description_stripped.length > 0 && ( -

- {block.description_stripped} -

- )} + {/* {showBlockDetails */} + {/* ? block.description_html.length > 7 && ( */} + {/* */} + {/* ) */} + {/* : block.description_stripped.length > 0 && ( */} + {/*

*/} + {/* {block.description_stripped} */} + {/*

*/} + {/* )} */}
Date: Wed, 2 Aug 2023 23:04:55 +0530 Subject: [PATCH 02/27] styles migrated for remirror to tiptap transition --- .../components/issues/description-form.tsx | 75 +++++++++++-------- apps/app/components/issues/form.tsx | 64 ++++++++-------- apps/app/components/issues/props.tsx | 7 ++ apps/app/components/issues/tiptap.tsx | 43 +++++++++++ apps/app/styles/editor.css | 8 ++ 5 files changed, 132 insertions(+), 65 deletions(-) create mode 100644 apps/app/components/issues/props.tsx create mode 100644 apps/app/components/issues/tiptap.tsx diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index e81c8c1b3..fb7d574ee 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -8,14 +8,16 @@ import { Controller, useForm } from "react-hook-form"; import useReloadConfirmations from "hooks/use-reload-confirmation"; // components import { Loader, TextArea } from "components/ui"; -const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { - ssr: false, - loading: () => ( - - - - ), -}); +// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { +// ssr: false, +// loading: () => ( +// +// +// +// ), +// }); + +import Tiptap from "./tiptap"; // types import { IIssue } from "types"; @@ -106,9 +108,8 @@ export const IssueDescriptionForm: FC = ({ {characterLimit && (
255 ? "text-red-500" : "" - }`} + className={`${watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : "" + }`} > {watch("name").length} @@ -125,31 +126,39 @@ export const IssueDescriptionForm: FC = ({ if (!value && !watch("description_html")) return <>; return ( - { - setShowAlert(true); - setValue("description", jsonValue); - }} - onHTMLChange={(htmlValue) => { - setShowAlert(true); - setValue("description_html", htmlValue); - }} - onBlur={() => { - setIsSubmitting(true); - handleSubmit(handleDescriptionFormSubmit)() - .then(() => setShowAlert(false)) - .finally(() => setIsSubmitting(false)); - }} - placeholder="Description" - editable={isAllowed} + ? watch("description_html") + : value + } /> + // { + // setShowAlert(true); + // setValue("description", jsonValue); + // }} + // onHTMLChange={(htmlValue) => { + // setShowAlert(true); + // setValue("description_html", htmlValue); + // }} + // onBlur={() => { + // setIsSubmitting(true); + // handleSubmit(handleDescriptionFormSubmit)() + // .then(() => setShowAlert(false)) + // .finally(() => setIsSubmitting(false)); + // }} + // placeholder="Description" + // editable={isAllowed} + // /> ); }} /> diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index e6fc11ae2..e16bd317f 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -37,23 +37,23 @@ import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types import type { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types"; // rich-text-editor -const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { - ssr: false, - loading: () => ( - - - - ), -}); +// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { +// ssr: false, +// loading: () => ( +// +// +// +// ), +// }); -import { IRemirrorRichTextEditor } from "components/rich-text-editor"; +// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; -const WrappedRemirrorRichTextEditor = React.forwardRef< - IRemirrorRichTextEditor, - IRemirrorRichTextEditor ->((props, ref) => ); - -WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +// const WrappedRemirrorRichTextEditor = React.forwardRef< +// IRemirrorRichTextEditor, +// IRemirrorRichTextEditor +// >((props, ref) => ); +// +// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; const defaultValues: Partial = { project: "", @@ -362,23 +362,23 @@ export const IssueForm: FC = ({ AI
- ( - setValue("description", jsonValue)} - onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - placeholder="Description" - ref={editorRef} - /> - )} - /> + {/* ( */} + {/* setValue("description", jsonValue)} */} + {/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */} + {/* placeholder="Description" */} + {/* ref={editorRef} */} + {/* /> */} + {/* )} */} + {/* /> */} { diff --git a/apps/app/components/issues/props.tsx b/apps/app/components/issues/props.tsx new file mode 100644 index 000000000..54306429c --- /dev/null +++ b/apps/app/components/issues/props.tsx @@ -0,0 +1,7 @@ +import { EditorProps } from "@tiptap/pm/view"; + +export const TiptapEditorProps: EditorProps = { + attributes: { + class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`, + } +}; diff --git a/apps/app/components/issues/tiptap.tsx b/apps/app/components/issues/tiptap.tsx new file mode 100644 index 000000000..d3f554d4c --- /dev/null +++ b/apps/app/components/issues/tiptap.tsx @@ -0,0 +1,43 @@ +import Placeholder from '@tiptap/extension-placeholder'; +import { useEditor, EditorContent } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import { EditorBubbleMenu } from './EditorBubbleMenu'; +import { TiptapEditorProps } from "./props"; + +type TiptapProps = { + value: string; + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; +} + +const Tiptap = ({ value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { + const editor = useEditor({ + editorProps: TiptapEditorProps, + extensions: [ + StarterKit, + Placeholder.configure({ + placeholder: 'Description...', + }) + ], + content: value, + }); + + const editorClassNames = `mt-2 p-3 relative focus:outline-none rounded-md focus:border-custom-border-200 + ${noBorder ? '' : 'border border-custom-border-200' + } ${borderOnFocus ? 'focus:border border-custom-border-200' : 'focus:border-0' + } ${customClassName}`; + + return ( +
{ + editor?.chain().focus().run(); + }} + className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`} + > + +
+ ); +}; + +export default Tiptap; diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css index ea4b2e601..293a017b2 100644 --- a/apps/app/styles/editor.css +++ b/apps/app/styles/editor.css @@ -8,6 +8,14 @@ margin-left: 1px; } +.ProseMirror p.is-editor-empty:first-child::before { + color: rgb(var(--color-text-400)); + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; +} + .ProseMirror { position: relative; word-wrap: break-word; From b078e24d828c68782302a52b111d5ce6900b9445 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 3 Aug 2023 00:15:02 +0530 Subject: [PATCH 03/27] added bubblemenu support with extensions --- .../components/issues/EditorBubbleMenu.tsx | 116 +++++++++++++++ apps/app/components/issues/extensions.tsx | 127 ++++++++++++++++ apps/app/components/issues/link-selector.tsx | 78 ++++++++++ apps/app/components/issues/node-selector.tsx | 135 ++++++++++++++++++ apps/app/components/issues/props.tsx | 13 +- apps/app/components/issues/tiptap.tsx | 15 +- apps/app/components/issues/utils.ts | 6 + apps/app/package.json | 12 ++ apps/app/styles/editor.css | 91 +++++++++++- 9 files changed, 584 insertions(+), 9 deletions(-) create mode 100644 apps/app/components/issues/EditorBubbleMenu.tsx create mode 100644 apps/app/components/issues/extensions.tsx create mode 100644 apps/app/components/issues/link-selector.tsx create mode 100644 apps/app/components/issues/node-selector.tsx create mode 100644 apps/app/components/issues/utils.ts diff --git a/apps/app/components/issues/EditorBubbleMenu.tsx b/apps/app/components/issues/EditorBubbleMenu.tsx new file mode 100644 index 000000000..7945c4589 --- /dev/null +++ b/apps/app/components/issues/EditorBubbleMenu.tsx @@ -0,0 +1,116 @@ +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 "./utils"; + +export interface BubbleMenuItem { + name: string; + isActive: () => boolean; + command: () => void; + icon: typeof BoldIcon; +} + +type EditorBubbleMenuProps = Omit; + +export const EditorBubbleMenu: FC = (props) => { + 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.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 ( + + { + setIsNodeSelectorOpen(!isNodeSelectorOpen); + setIsLinkSelectorOpen(false); + }} + /> + { + setIsLinkSelectorOpen(!isLinkSelectorOpen); + setIsNodeSelectorOpen(false); + }} + /> +
+ {items.map((item, index) => ( + + ))} +
+
+ ); +}; diff --git a/apps/app/components/issues/extensions.tsx b/apps/app/components/issues/extensions.tsx new file mode 100644 index 000000000..7e5a2d6a6 --- /dev/null +++ b/apps/app/components/issues/extensions.tsx @@ -0,0 +1,127 @@ +import StarterKit from "@tiptap/starter-kit"; +import HorizontalRule from "@tiptap/extension-horizontal-rule"; +import TiptapLink from "@tiptap/extension-link"; +import TiptapImage from "@tiptap/extension-image"; +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 SlashCommand from "./slash-command"; +import { InputRule } from "@tiptap/core"; + +export const TiptapExtensions = [ + 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-stone-700", + }, + }, + codeBlock: { + HTMLAttributes: { + class: + "rounded-sm bg-stone-100 p-5 font-mono font-medium text-stone-800", + }, + }, + code: { + HTMLAttributes: { + class: + "rounded-md bg-stone-200 px-1.5 py-1 font-mono font-medium text-stone-900", + spellcheck: "false", + }, + }, + horizontalRule: false, + dropcursor: { + color: "#DBEAFE", + width: 4, + }, + gapcursor: false, + }), + HorizontalRule.extend({ + addInputRules() { + return [ + new InputRule({ + find: /^(?:---|—-|___\s|\*\*\*\s)$/, + handler: ({ state, range }) => { + const attributes = {}; + + const { tr } = state; + const start = range.from; + const end = range.to; + + tr.insert(start - 1, this.type.create(attributes)).delete( + tr.mapping.map(start), + tr.mapping.map(end), + ); + }, + }), + ]; + }, + }).configure({ + HTMLAttributes: { + class: "mt-4 mb-6 border-t border-stone-300", + }, + }), + TiptapLink.configure({ + HTMLAttributes: { + class: + "text-stone-400 underline underline-offset-[3px] hover:text-stone-600 transition-colors cursor-pointer", + }, + }), + TiptapImage.configure({ + allowBase64: true, + HTMLAttributes: { + class: "rounded-lg border border-stone-200", + }, + }), + Placeholder.configure({ + placeholder: ({ node }) => { + if (node.type.name === "heading") { + return `Heading ${node.attrs.level}`; + } + return "Press '/' for commands, or '++' for AI autocomplete..."; + }, + includeChildren: true, + }), + // SlashCommand, + TiptapUnderline, + TextStyle, + Color, + Highlight.configure({ + multicolor: true, + }), + TaskList.configure({ + HTMLAttributes: { + class: "not-prose pl-2", + }, + }), + TaskItem.configure({ + HTMLAttributes: { + class: "flex items-start my-4", + }, + nested: true, + }), + Markdown.configure({ + html: false, + transformCopiedText: true, + }), +]; diff --git a/apps/app/components/issues/link-selector.tsx b/apps/app/components/issues/link-selector.tsx new file mode 100644 index 000000000..7bc3ea2e9 --- /dev/null +++ b/apps/app/components/issues/link-selector.tsx @@ -0,0 +1,78 @@ +import { Editor } from "@tiptap/core"; +import { Check, Trash } from "lucide-react"; +import { Dispatch, FC, SetStateAction, useEffect, useRef } from "react"; +import { cn } from './utils'; + +interface LinkSelectorProps { + editor: Editor; + isOpen: boolean; + setIsOpen: Dispatch>; +} + +export const LinkSelector: FC = ({ + editor, + isOpen, + setIsOpen, +}) => { + const inputRef = useRef(null); + + // Autofocus on input by default + useEffect(() => { + inputRef.current && inputRef.current?.focus(); + }); + + return ( +
+ + {isOpen && ( +
{ + e.preventDefault(); + const input = e.target[0] as HTMLInputElement; + editor.chain().focus().setLink({ href: input.value }).run(); + setIsOpen(false); + }} + className="fixed top-full z-[99999] mt-1 flex w-60 overflow-hidden rounded border border-stone-200 bg-white p-1 shadow-xl animate-in fade-in slide-in-from-top-1" + > + + {editor.getAttributes("link").href ? ( + + ) : ( + + )} +
+ )} +
+ ); +}; + diff --git a/apps/app/components/issues/node-selector.tsx b/apps/app/components/issues/node-selector.tsx new file mode 100644 index 000000000..4591e8707 --- /dev/null +++ b/apps/app/components/issues/node-selector.tsx @@ -0,0 +1,135 @@ +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 "./EditorBubbleMenu"; + +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(), + // I feel like there has to be a more efficient way to do this – feel free to PR if you know how! + isActive: () => + editor.isActive("paragraph") && + !editor.isActive("bulletList") && + !editor.isActive("orderedList"), + }, + { + name: "Heading 1", + icon: Heading1, + command: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), + isActive: () => editor.isActive("heading", { level: 1 }), + }, + { + name: "Heading 2", + icon: Heading2, + command: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), + isActive: () => editor.isActive("heading", { level: 2 }), + }, + { + name: "Heading 3", + 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/apps/app/components/issues/props.tsx b/apps/app/components/issues/props.tsx index 54306429c..6470b37c1 100644 --- a/apps/app/components/issues/props.tsx +++ b/apps/app/components/issues/props.tsx @@ -3,5 +3,16 @@ import { EditorProps } from "@tiptap/pm/view"; export const TiptapEditorProps: 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; + } + } + }, + }, }; diff --git a/apps/app/components/issues/tiptap.tsx b/apps/app/components/issues/tiptap.tsx index d3f554d4c..9e298ef3c 100644 --- a/apps/app/components/issues/tiptap.tsx +++ b/apps/app/components/issues/tiptap.tsx @@ -2,6 +2,7 @@ import Placeholder from '@tiptap/extension-placeholder'; import { useEditor, EditorContent } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import { EditorBubbleMenu } from './EditorBubbleMenu'; +import { TiptapExtensions } from './extensions'; import { TiptapEditorProps } from "./props"; type TiptapProps = { @@ -14,12 +15,13 @@ type TiptapProps = { const Tiptap = ({ value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { const editor = useEditor({ editorProps: TiptapEditorProps, - extensions: [ - StarterKit, - Placeholder.configure({ - placeholder: 'Description...', - }) - ], + extensions: TiptapExtensions, + // extensions: [ + // StarterKit, + // Placeholder.configure({ + // placeholder: 'Description...', + // }) + // ], content: value, }); @@ -35,6 +37,7 @@ const Tiptap = ({ value, noBorder, borderOnFocus, customClassName }: TiptapProps }} className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`} > + {editor && }
); diff --git a/apps/app/components/issues/utils.ts b/apps/app/components/issues/utils.ts new file mode 100644 index 000000000..a5ef19350 --- /dev/null +++ b/apps/app/components/issues/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/apps/app/package.json b/apps/app/package.json index 8c8c0d1f7..eb8bbaae0 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -27,17 +27,27 @@ "@nivo/scatterplot": "0.80.0", "@sentry/nextjs": "^7.36.0", "@tailwindcss/typography": "^0.5.9", + "@tiptap/extension-color": "^2.0.4", + "@tiptap/extension-highlight": "^2.0.4", + "@tiptap/extension-image": "^2.0.4", + "@tiptap/extension-link": "^2.0.4", "@tiptap/extension-placeholder": "^2.0.4", + "@tiptap/extension-task-item": "^2.0.4", + "@tiptap/extension-task-list": "^2.0.4", + "@tiptap/extension-text-style": "^2.0.4", + "@tiptap/extension-underline": "^2.0.4", "@tiptap/pm": "^2.0.4", "@tiptap/react": "^2.0.4", "@tiptap/starter-kit": "^2.0.4", "@types/lodash.debounce": "^4.0.7", "@types/react-datepicker": "^4.8.0", "axios": "^1.1.3", + "clsx": "^2.0.0", "cmdk": "^0.2.0", "dotenv": "^16.0.3", "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", + "lucide-react": "^0.263.1", "next": "12.3.2", "next-pwa": "^5.6.0", "next-themes": "^0.2.1", @@ -51,6 +61,8 @@ "react-hook-form": "^7.38.0", "react-markdown": "^8.0.7", "swr": "^2.1.3", + "tailwind-merge": "^1.14.0", + "tiptap-markdown": "^0.8.2", "tlds": "^1.238.0", "uuid": "^9.0.0" }, diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css index 293a017b2..6f45ecc2a 100644 --- a/apps/app/styles/editor.css +++ b/apps/app/styles/editor.css @@ -9,13 +9,100 @@ } .ProseMirror p.is-editor-empty:first-child::before { - color: rgb(var(--color-text-400)); content: attr(data-placeholder); float: left; - height: 0; + color: rgb(var(--color-text-400)); pointer-events: none; + height: 0; } +.ProseMirror .is-empty::before { + content: attr(data-placeholder); + float: left; + color: rgb(var(--color-text-400)); + pointer-events: none; + height: 0; +} + +/* Custom image styles */ + +.ProseMirror img { + transition: filter 0.1s ease-in-out; + + &:hover { + cursor: pointer; + filter: brightness(90%); + } + + &.ProseMirror-selectednode { + outline: 3px solid #5abbf7; + filter: brightness(90%); + } +} + +/* Custom TODO list checkboxes – shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */ + +ul[data-type="taskList"] li > label { + margin-right: 0.2rem; + user-select: none; +} + +@media screen and (max-width: 768px) { + ul[data-type="taskList"] li > label { + margin-right: 0.5rem; + } +} + +ul[data-type="taskList"] li > label input[type="checkbox"] { + -webkit-appearance: none; + appearance: none; + background-color: var(--novel-white); + margin: 0; + cursor: pointer; + width: 1.2em; + height: 1.2em; + position: relative; + top: 5px; + border: 2px solid var(--novel-stone-900); + margin-right: 0.3rem; + display: grid; + place-content: center; + + &:hover { + background-color: var(--novel-stone-50); + } + + &:active { + background-color: var(--novel-stone-200); + } + + &::before { + content: ""; + width: 0.65em; + height: 0.65em; + transform: scale(0); + transition: 120ms transform ease-in-out; + box-shadow: inset 1em 1em; + transform-origin: center; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + } + + &:checked::before { + transform: scale(1); + } +} + +ul[data-type="taskList"] li[data-checked="true"] > div > p { + color: var(--novel-stone-400); + text-decoration: line-through; + text-decoration-thickness: 2px; +} + +/* Overwrite tippy-box original max-width */ + +.tippy-box { + max-width: 400px !important; +} .ProseMirror { position: relative; word-wrap: break-word; From 727570e34742408bf3d83a38b5f695192a5827e0 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Sat, 5 Aug 2023 04:50:18 +0530 Subject: [PATCH 04/27] fixed css for task lists and code with syntax highlighting --- apps/app/postcss.config.js | 1 + apps/app/styles/editor.css | 50 +++++++++++++++++++++---------------- apps/app/tailwind.config.js | 5 +++- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/apps/app/postcss.config.js b/apps/app/postcss.config.js index 12a703d90..cbfea5ea2 100644 --- a/apps/app/postcss.config.js +++ b/apps/app/postcss.config.js @@ -1,5 +1,6 @@ module.exports = { plugins: { + "tailwindcss/nesting": {}, tailwindcss: {}, autoprefixer: {}, }, diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css index 6f45ecc2a..f8f91ca34 100644 --- a/apps/app/styles/editor.css +++ b/apps/app/styles/editor.css @@ -1,13 +1,3 @@ -.empty-node::after { - content: attr(data-placeholder); - color: rgb(var(--color-text-400)); - - position: absolute; - pointer-events: none; - top: 15px; - margin-left: 1px; -} - .ProseMirror p.is-editor-empty:first-child::before { content: attr(data-placeholder); float: left; @@ -42,38 +32,37 @@ /* Custom TODO list checkboxes – shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */ -ul[data-type="taskList"] li > label { +ul[data-type="taskList"] li>label { margin-right: 0.2rem; user-select: none; } @media screen and (max-width: 768px) { - ul[data-type="taskList"] li > label { + ul[data-type="taskList"] li>label { margin-right: 0.5rem; } } -ul[data-type="taskList"] li > label input[type="checkbox"] { +ul[data-type="taskList"] li>label input[type="checkbox"] { -webkit-appearance: none; appearance: none; - background-color: var(--novel-white); + background-color: rgb(var(--color-background-100)); margin: 0; cursor: pointer; - width: 1.2em; - height: 1.2em; + width: 1.2rem; + height: 1.2rem; position: relative; - top: 5px; - border: 2px solid var(--novel-stone-900); + border: 2px solid rgb(var(--color-text-100)); margin-right: 0.3rem; display: grid; place-content: center; &:hover { - background-color: var(--novel-stone-50); + background-color: rgb(var(--color-background-80)); } &:active { - background-color: var(--novel-stone-200); + background-color: rgb(var(--color-background-90)); } &::before { @@ -92,8 +81,8 @@ ul[data-type="taskList"] li > label input[type="checkbox"] { } } -ul[data-type="taskList"] li[data-checked="true"] > div > p { - color: var(--novel-stone-400); +ul[data-type="taskList"] li[data-checked="true"]>div>p { + color: rgb(var(--color-text-200)); text-decoration: line-through; text-decoration-thickness: 2px; } @@ -103,6 +92,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p { .tippy-box { max-width: 400px !important; } + .ProseMirror { position: relative; word-wrap: break-word; @@ -126,6 +116,22 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p { -moz-appearance: textfield; } +.ProseMirror pre { + background: #121212; + border-radius: 0.375rem; + border-color: rgba(var(--color-background-100)); + border: 0.5px solid; + font-family: "JetBrainsMono", monospace; + padding: 0.75rem 1rem; +} + +.ProseMirror pre code { + background: none; + color: inherit; + font-size: 0.8rem; + padding: 0; +} + .ProseMirror-icon { display: inline-block; line-height: 0.8; diff --git a/apps/app/tailwind.config.js b/apps/app/tailwind.config.js index fbe0994b4..11e1946a5 100644 --- a/apps/app/tailwind.config.js +++ b/apps/app/tailwind.config.js @@ -182,5 +182,8 @@ module.exports = { custom: ["Inter", "sans-serif"], }, }, - plugins: [require("@tailwindcss/typography")], + plugins: [ + require("tailwindcss-animate"), + require("@tailwindcss/typography") + ], }; From 60f1b7346d21465d343b60162b263d433fb5d057 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Sat, 5 Aug 2023 04:50:59 +0530 Subject: [PATCH 05/27] added support for slash command --- apps/app/components/issues/slash-command.tsx | 350 +++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 apps/app/components/issues/slash-command.tsx diff --git a/apps/app/components/issues/slash-command.tsx b/apps/app/components/issues/slash-command.tsx new file mode 100644 index 000000000..d869001cd --- /dev/null +++ b/apps/app/components/issues/slash-command.tsx @@ -0,0 +1,350 @@ +import React, { + 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, +} from "lucide-react"; + +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, + ...this.options.suggestion, + }), + ]; + }, +}); + +const getSuggestionItems = ({ 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) => { + 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: "Numbered List", + description: "Create a list with numbering.", + searchTerms: ["ordered"], + icon: , + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).toggleOrderedList().run(); + }, + }, + { + title: "Quote", + description: "Capture a quote.", + searchTerms: ["blockquote"], + icon: , + command: ({ editor, range }: CommandProps) => + 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(), + }, + ].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, + editor, + range, +}: { + 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.body, + 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(); + }, + }; +}; + +const SlashCommand = Command.configure({ + suggestion: { + items: getSuggestionItems, + render: renderItems, + }, +}); + +export default SlashCommand; From 50e7c5924c906bf521748ebc4623fcec3217f20a Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Sat, 5 Aug 2023 04:51:58 +0530 Subject: [PATCH 06/27] fixed bubble menu to match styles and added better seperation in UI --- .../components/issues/EditorBubbleMenu.tsx | 4 +-- apps/app/components/issues/extensions.tsx | 30 +++++++++++-------- apps/app/components/issues/link-selector.tsx | 11 ++++--- apps/app/components/issues/node-selector.tsx | 21 ++++++------- apps/app/package.json | 8 +++++ 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/apps/app/components/issues/EditorBubbleMenu.tsx b/apps/app/components/issues/EditorBubbleMenu.tsx index 7945c4589..32ad3f2a9 100644 --- a/apps/app/components/issues/EditorBubbleMenu.tsx +++ b/apps/app/components/issues/EditorBubbleMenu.tsx @@ -78,7 +78,7 @@ export const EditorBubbleMenu: FC = (props) => { return ( = (props) => { ) : ( - )} diff --git a/apps/app/components/issues/node-selector.tsx b/apps/app/components/issues/node-selector.tsx index 4591e8707..ebb9767ec 100644 --- a/apps/app/components/issues/node-selector.tsx +++ b/apps/app/components/issues/node-selector.tsx @@ -14,6 +14,7 @@ import { import { Dispatch, FC, SetStateAction } from "react"; import { BubbleMenuItem } from "./EditorBubbleMenu"; +import { cn } from "./utils"; interface NodeSelectorProps { editor: Editor; @@ -32,26 +33,25 @@ export const NodeSelector: FC = ({ icon: TextIcon, command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(), - // I feel like there has to be a more efficient way to do this – feel free to PR if you know how! isActive: () => editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"), }, { - name: "Heading 1", + name: "H1", icon: Heading1, command: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), isActive: () => editor.isActive("heading", { level: 1 }), }, { - name: "Heading 2", + name: "H2", icon: Heading2, command: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), isActive: () => editor.isActive("heading", { level: 2 }), }, { - name: "Heading 3", + name: "H3", icon: Heading3, command: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), isActive: () => editor.isActive("heading", { level: 3 }), @@ -101,7 +101,7 @@ export const NodeSelector: FC = ({ return (
{isOpen && ( -
+
{items.map((item, index) => ( ))}
- )} -
+ ) + } + ); }; diff --git a/apps/app/package.json b/apps/app/package.json index eb8bbaae0..77860d7d0 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -27,8 +27,10 @@ "@nivo/scatterplot": "0.80.0", "@sentry/nextjs": "^7.36.0", "@tailwindcss/typography": "^0.5.9", + "@tiptap/extension-code-block-lowlight": "^2.0.4", "@tiptap/extension-color": "^2.0.4", "@tiptap/extension-highlight": "^2.0.4", + "@tiptap/extension-horizontal-rule": "^2.0.4", "@tiptap/extension-image": "^2.0.4", "@tiptap/extension-link": "^2.0.4", "@tiptap/extension-placeholder": "^2.0.4", @@ -39,14 +41,17 @@ "@tiptap/pm": "^2.0.4", "@tiptap/react": "^2.0.4", "@tiptap/starter-kit": "^2.0.4", + "@tiptap/suggestion": "^2.0.4", "@types/lodash.debounce": "^4.0.7", "@types/react-datepicker": "^4.8.0", "axios": "^1.1.3", "clsx": "^2.0.0", "cmdk": "^0.2.0", "dotenv": "^16.0.3", + "highlight.js": "^11.8.0", "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", + "lowlight": "^2.9.0", "lucide-react": "^0.263.1", "next": "12.3.2", "next-pwa": "^5.6.0", @@ -60,10 +65,13 @@ "react-dropzone": "^14.2.3", "react-hook-form": "^7.38.0", "react-markdown": "^8.0.7", + "sonner": "^0.6.2", "swr": "^2.1.3", "tailwind-merge": "^1.14.0", + "tailwindcss-animate": "^1.0.6", "tiptap-markdown": "^0.8.2", "tlds": "^1.238.0", + "use-debounce": "^9.0.4", "uuid": "^9.0.0" }, "devDependencies": { From a6ae849a810a432aff60cee45f3704e40a1e2dce Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Sat, 5 Aug 2023 04:53:10 +0530 Subject: [PATCH 07/27] saving with debounce logic added and it's stored in backend --- .../components/issues/description-form.tsx | 69 +++++-------------- apps/app/components/issues/tiptap.tsx | 25 ++++--- 2 files changed, 35 insertions(+), 59 deletions(-) diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index fb7d574ee..4353036b3 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -1,21 +1,11 @@ import { FC, useCallback, useEffect, useState } from "react"; -import dynamic from "next/dynamic"; - // react-hook-form import { Controller, useForm } from "react-hook-form"; // hooks import useReloadConfirmations from "hooks/use-reload-confirmation"; // components -import { Loader, TextArea } from "components/ui"; -// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { -// ssr: false, -// loading: () => ( -// -// -// -// ), -// }); +import { TextArea } from "components/ui"; import Tiptap from "./tiptap"; // types @@ -65,7 +55,8 @@ export const IssueDescriptionForm: FC = ({ const handleDescriptionFormSubmit = useCallback( async (formData: Partial) => { - if (!formData.name || formData.name.length === 0 || formData.name.length > 255) return; + console.log("formdata", formData) + if (!formData?.name || formData?.name.length === 0 || formData?.name.length > 255) return; await handleFormSubmit({ name: formData.name ?? "", @@ -122,51 +113,29 @@ export const IssueDescriptionForm: FC = ({ { + render={({ field: { value, onChange } }) => { if (!value && !watch("description_html")) return <>; return ( - { + onChange(description); + setValue("description_html", description_html); + handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting(false)); + }} /> - // { - // setShowAlert(true); - // setValue("description", jsonValue); - // }} - // onHTMLChange={(htmlValue) => { - // setShowAlert(true); - // setValue("description_html", htmlValue); - // }} - // onBlur={() => { - // setIsSubmitting(true); - // handleSubmit(handleDescriptionFormSubmit)() - // .then(() => setShowAlert(false)) - // .finally(() => setIsSubmitting(false)); - // }} - // placeholder="Description" - // editable={isAllowed} - // /> ); }} /> - {isSubmitting && ( -
- Saving... -
- )} +
+ {isSubmitting ? "Saving..." : "Saved"} +
); diff --git a/apps/app/components/issues/tiptap.tsx b/apps/app/components/issues/tiptap.tsx index 9e298ef3c..8909e4de1 100644 --- a/apps/app/components/issues/tiptap.tsx +++ b/apps/app/components/issues/tiptap.tsx @@ -1,6 +1,5 @@ -import Placeholder from '@tiptap/extension-placeholder'; import { useEditor, EditorContent } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; +import { useDebouncedCallback } from 'use-debounce'; import { EditorBubbleMenu } from './EditorBubbleMenu'; import { TiptapExtensions } from './extensions'; import { TiptapEditorProps } from "./props"; @@ -10,21 +9,29 @@ type TiptapProps = { noBorder?: boolean; borderOnFocus?: boolean; customClassName?: string; + onChange?: (json: any, html: string) => void; + setIsSubmitting?: (isSubmitting: boolean) => void; } -const Tiptap = ({ value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { +const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { const editor = useEditor({ editorProps: TiptapEditorProps, extensions: TiptapExtensions, - // extensions: [ - // StarterKit, - // Placeholder.configure({ - // placeholder: 'Description...', - // }) - // ], content: value, + onUpdate: async ({ editor }) => { + setIsSubmitting(true); + debouncedUpdates({ onChange, editor }); + } }); + const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => { + setTimeout(async () => { + if (onChange) { + onChange(editor.getJSON(), editor.getHTML()); + } + }, 500); + }, 1000); + const editorClassNames = `mt-2 p-3 relative focus:outline-none rounded-md focus:border-custom-border-200 ${noBorder ? '' : 'border border-custom-border-200' } ${borderOnFocus ? 'focus:border border-custom-border-200' : 'focus:border-0' From 63c7bc2d684e395a484e6cef9e2526372b2882cb Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Mon, 7 Aug 2023 21:02:39 +0530 Subject: [PATCH 08/27] added migration support by updating to html --- apps/app/components/command-palette/command-pallette.tsx | 2 +- apps/app/components/issues/description-form.tsx | 7 ++++--- apps/app/components/issues/extensions.tsx | 4 ++-- apps/app/components/issues/tiptap.tsx | 9 ++++++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index 2a8a4aa4d..0d5b37ada 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -82,7 +82,7 @@ export const CommandPalette: React.FC = () => { !(e.target instanceof HTMLTextAreaElement) && !(e.target instanceof HTMLInputElement) && // !(e.target as Element).classList?.contains("remirror-editor") && - !(e.target as Element).closest(".tiptap-editor-container") + !(e.target as Element)?.closest(".tiptap-editor-container") ) { if ((ctrlKey || metaKey) && keyPressed === "k") { e.preventDefault(); diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index 4353036b3..d190a4092 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -111,7 +111,7 @@ export const IssueDescriptionForm: FC = ({ {errors.name ? errors.name.message : null}
{ if (!value && !watch("description_html")) return <>; @@ -125,8 +125,9 @@ export const IssueDescriptionForm: FC = ({ } setIsSubmitting={setIsSubmitting} onChange={(description: Object, description_html: string) => { - onChange(description); - setValue("description_html", description_html); + onChange(description_html); + // setValue("description_html", description_html); + setValue("description", description); handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting(false)); }} /> diff --git a/apps/app/components/issues/extensions.tsx b/apps/app/components/issues/extensions.tsx index 15e59a1c5..403495fa4 100644 --- a/apps/app/components/issues/extensions.tsx +++ b/apps/app/components/issues/extensions.tsx @@ -104,7 +104,7 @@ export const TiptapExtensions = [ return `Heading ${node.attrs.level}`; } - return "Press '/' for commands, or 'Ctrl + Space' for AI autocomplete..."; + return "Press '/' for commands..."; }, includeChildren: true, }), @@ -127,7 +127,7 @@ export const TiptapExtensions = [ nested: true, }), Markdown.configure({ - html: false, + html: true, transformCopiedText: true, }), ]; diff --git a/apps/app/components/issues/tiptap.tsx b/apps/app/components/issues/tiptap.tsx index 8909e4de1..67380a742 100644 --- a/apps/app/components/issues/tiptap.tsx +++ b/apps/app/components/issues/tiptap.tsx @@ -1,4 +1,5 @@ -import { useEditor, EditorContent } from '@tiptap/react'; +import { useEditor, EditorContent, generateText } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; import { useDebouncedCallback } from 'use-debounce'; import { EditorBubbleMenu } from './EditorBubbleMenu'; import { TiptapExtensions } from './extensions'; @@ -10,7 +11,7 @@ type TiptapProps = { borderOnFocus?: boolean; customClassName?: string; onChange?: (json: any, html: string) => void; - setIsSubmitting?: (isSubmitting: boolean) => void; + setIsSubmitting: (isSubmitting: boolean) => void; } const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { @@ -45,7 +46,9 @@ const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, cus className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`} > {editor && } - +
+ +
); }; From c078d59916f75755a6d3229ec75441c4ec56785b Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:20:19 +0530 Subject: [PATCH 09/27] Image uploads done --- .../command-palette/command-pallette.tsx | 2 +- apps/app/components/issues/extensions.tsx | 9 +- .../issues/plugins/UploadHelper.tsx | 28 +++++ .../issues/plugins/upload-image.tsx | 119 ++++++++++++++++++ apps/app/components/issues/props.ts | 54 ++++++++ apps/app/components/issues/props.tsx | 18 --- apps/app/components/issues/tiptap.tsx | 5 +- .../projects/[projectId]/issues/[issueId].tsx | 1 + 8 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 apps/app/components/issues/plugins/UploadHelper.tsx create mode 100644 apps/app/components/issues/plugins/upload-image.tsx create mode 100644 apps/app/components/issues/props.ts delete mode 100644 apps/app/components/issues/props.tsx diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index 0d5b37ada..151002c9e 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -82,7 +82,7 @@ export const CommandPalette: React.FC = () => { !(e.target instanceof HTMLTextAreaElement) && !(e.target instanceof HTMLInputElement) && // !(e.target as Element).classList?.contains("remirror-editor") && - !(e.target as Element)?.closest(".tiptap-editor-container") + (e.target === document || (e.target instanceof Element && !e.target.closest(".tiptap-editor-container"))) ) { if ((ctrlKey || metaKey) && keyPressed === "k") { e.preventDefault(); diff --git a/apps/app/components/issues/extensions.tsx b/apps/app/components/issues/extensions.tsx index 403495fa4..f34d32fe4 100644 --- a/apps/app/components/issues/extensions.tsx +++ b/apps/app/components/issues/extensions.tsx @@ -18,9 +18,16 @@ import { InputRule } from "@tiptap/core"; import ts from 'highlight.js/lib/languages/typescript' import 'highlight.js/styles/github-dark.css'; +import UploadImagesPlugin from "./plugins/upload-image"; lowlight.registerLanguage('ts', ts) +const CustomImage = TiptapImage.extend({ + addProseMirrorPlugins() { + return [UploadImagesPlugin()]; + }, +}); + export const TiptapExtensions = [ StarterKit.configure({ bulletList: { @@ -92,7 +99,7 @@ export const TiptapExtensions = [ "text-stone-400 underline underline-offset-[3px] hover:text-stone-600 transition-colors cursor-pointer", }, }), - TiptapImage.configure({ + CustomImage.configure({ allowBase64: true, HTMLAttributes: { class: "rounded-lg border border-stone-200", diff --git a/apps/app/components/issues/plugins/UploadHelper.tsx b/apps/app/components/issues/plugins/UploadHelper.tsx new file mode 100644 index 000000000..5a64d4c0a --- /dev/null +++ b/apps/app/components/issues/plugins/UploadHelper.tsx @@ -0,0 +1,28 @@ +import fileService from 'services/file.service'; + +const UploadImageHandler = (file: File): Promise => { + try { + const formData = new FormData(); + formData.append("asset", file); + formData.append("attributes", JSON.stringify({})); + + return new Promise(async (resolve, reject) => { + const imageUrl = await fileService + .uploadFile("plane", formData) + .then((response) => response.asset); + + console.log(imageUrl, "imageurl") + const image = new Image(); + image.src = imageUrl; + image.onload = () => { + resolve(imageUrl); + }; + }) + } + catch (error) { + console.log(error) + return Promise.reject(error); + } +}; + +export default UploadImageHandler; diff --git a/apps/app/components/issues/plugins/upload-image.tsx b/apps/app/components/issues/plugins/upload-image.tsx new file mode 100644 index 000000000..b1ea94cc2 --- /dev/null +++ b/apps/app/components/issues/plugins/upload-image.tsx @@ -0,0 +1,119 @@ +import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state"; +import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view"; +import fileService from "services/file.service"; + +const uploadKey = new PluginKey("upload-image"); + +const UploadImagesPlugin = () => + new Plugin({ + key: uploadKey, + state: { + init() { + return DecorationSet.empty; + }, + apply(tr, set) { + set = set.map(tr.mapping, tr.doc); + // See if the transaction adds or removes any placeholders + const action = tr.getMeta(this); + if (action && action.add) { + const { id, pos, src } = action.add; + + const placeholder = document.createElement("div"); + placeholder.setAttribute("class", "img-placeholder"); + const image = document.createElement("img"); + image.setAttribute( + "class", + "opacity-10 rounded-lg border border-stone-200", + ); + image.src = src; + placeholder.appendChild(image); + const deco = Decoration.widget(pos + 1, placeholder, { + id, + }); + set = set.add(tr.doc, [deco]); + } else if (action && action.remove) { + set = set.remove( + set.find(null, null, (spec) => spec.id == action.remove.id), + ); + } + return set; + }, + }, + props: { + decorations(state) { + return this.getState(state); + }, + }, + }); + +export default UploadImagesPlugin; + +function findPlaceholder(state: EditorState, id: {}) { + const decos = uploadKey.getState(state); + const found = decos.find(null, null, (spec) => spec.id == id); + return found.length ? found[0].from : null; +} + +export async function startImageUpload(file: File, view: EditorView, pos: number) { + if (!file.type.includes("image/")) { + return; + } else if (file.size / 1024 / 1024 > 20) { + return; + } + + const id = {}; + + const tr = view.state.tr; + if (!tr.selection.empty) tr.deleteSelection(); + + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + tr.setMeta(uploadKey, { + add: { + id, + pos, + src: reader.result, + }, + }); + view.dispatch(tr); + }; + + const src = await UploadImageHandler(file); + console.log(src, "src") + const { schema } = view.state; + pos = findPlaceholder(view.state, id); + + if (pos == null) return; + const imageSrc = typeof src === "object" ? reader.result : src; + + const node = schema.nodes.image.create({ src: imageSrc }); + const transaction = view.state.tr + .replaceWith(pos, pos, node) + .setMeta(uploadKey, { remove: { id } }); + view.dispatch(transaction); +} + +const UploadImageHandler = (file: File): Promise => { + try { + const formData = new FormData(); + formData.append("asset", file); + formData.append("attributes", JSON.stringify({})); + + return new Promise(async (resolve, reject) => { + const imageUrl = await fileService + .uploadFile("plane", formData) + .then((response) => response.asset); + + const image = new Image(); + image.src = imageUrl; + image.onload = () => { + resolve(imageUrl); + }; + }) + } + catch (error) { + console.log(error) + return Promise.reject(error); + } +}; diff --git a/apps/app/components/issues/props.ts b/apps/app/components/issues/props.ts new file mode 100644 index 000000000..ea7f71b59 --- /dev/null +++ b/apps/app/components/issues/props.ts @@ -0,0 +1,54 @@ +import { EditorProps } from "@tiptap/pm/view"; +import { startImageUpload } from "./plugins/upload-image"; + +export const TiptapEditorProps: 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: (view, event) => { + if ( + event.clipboardData && + event.clipboardData.files && + event.clipboardData.files[0] + ) { + event.preventDefault(); + const file = event.clipboardData.files[0]; + const pos = view.state.selection.from; + + startImageUpload(file, view, pos); + return true; + } + return false; + }, + handleDrop: (view, event, _slice, moved) => { + if ( + !moved && + event.dataTransfer && + event.dataTransfer.files && + event.dataTransfer.files[0] + ) { + event.preventDefault(); + const file = event.dataTransfer.files[0]; + const coordinates = view.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + // here we deduct 1 from the pos or else the image will create an extra node + startImageUpload(file, view, coordinates.pos - 1); + return true; + } + return false; + }, +}; + diff --git a/apps/app/components/issues/props.tsx b/apps/app/components/issues/props.tsx deleted file mode 100644 index 6470b37c1..000000000 --- a/apps/app/components/issues/props.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { EditorProps } from "@tiptap/pm/view"; - -export const TiptapEditorProps: 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; - } - } - }, - }, -}; diff --git a/apps/app/components/issues/tiptap.tsx b/apps/app/components/issues/tiptap.tsx index 67380a742..ddadc9d13 100644 --- a/apps/app/components/issues/tiptap.tsx +++ b/apps/app/components/issues/tiptap.tsx @@ -1,9 +1,8 @@ -import { useEditor, EditorContent, generateText } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; +import { useEditor, EditorContent } from '@tiptap/react'; import { useDebouncedCallback } from 'use-debounce'; import { EditorBubbleMenu } from './EditorBubbleMenu'; import { TiptapExtensions } from './extensions'; -import { TiptapEditorProps } from "./props"; +import { TiptapEditorProps } from './props'; type TiptapProps = { value: string; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index d5f7c8ec6..cf0aa5de7 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -42,6 +42,7 @@ const defaultValues = { const IssueDetailsPage: NextPage = () => { const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; + console.log(workspaceSlug, "workspaceSlug") const { user } = useUserAuth(); From 5228ab8d0a25c441c2333c35efd9ead7a1900458 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Wed, 9 Aug 2023 01:57:08 +0530 Subject: [PATCH 10/27] improved file structure and delete image function implemented --- .gitignore | 4 +- .../components/issues/description-form.tsx | 3 +- .../issues/plugins/UploadHelper.tsx | 28 ----- apps/app/components/issues/tiptap.tsx | 55 --------- .../bubble-menu/index.tsx} | 2 +- .../bubble-menu}/link-selector.tsx | 2 +- .../bubble-menu}/node-selector.tsx | 4 +- .../extensions/index.tsx} | 8 +- apps/app/components/tiptap/index.tsx | 110 ++++++++++++++++++ .../plugins/upload-image.tsx | 2 +- .../{issues/props.ts => tiptap/props.tsx} | 0 .../slash-command/index.tsx} | 23 ++++ .../components/{issues => tiptap}/utils.ts | 0 apps/app/package.json | 1 + apps/app/services/file.service.ts | 9 +- 15 files changed, 152 insertions(+), 99 deletions(-) delete mode 100644 apps/app/components/issues/plugins/UploadHelper.tsx delete mode 100644 apps/app/components/issues/tiptap.tsx rename apps/app/components/{issues/EditorBubbleMenu.tsx => tiptap/bubble-menu/index.tsx} (99%) rename apps/app/components/{issues => tiptap/bubble-menu}/link-selector.tsx (98%) rename apps/app/components/{issues => tiptap/bubble-menu}/node-selector.tsx (97%) rename apps/app/components/{issues/extensions.tsx => tiptap/extensions/index.tsx} (93%) create mode 100644 apps/app/components/tiptap/index.tsx rename apps/app/components/{issues => tiptap}/plugins/upload-image.tsx (99%) rename apps/app/components/{issues/props.ts => tiptap/props.tsx} (100%) rename apps/app/components/{issues/slash-command.tsx => tiptap/slash-command/index.tsx} (92%) rename apps/app/components/{issues => tiptap}/utils.ts (100%) diff --git a/.gitignore b/.gitignore index 921881df4..1e99e102a 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,6 @@ package-lock.json # lock files package-lock.json pnpm-lock.yaml -pnpm-workspace.yaml \ No newline at end of file +pnpm-workspace.yaml + +.npmrc diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index d190a4092..fcf10073d 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -7,9 +7,9 @@ import useReloadConfirmations from "hooks/use-reload-confirmation"; // components import { TextArea } from "components/ui"; -import Tiptap from "./tiptap"; // types import { IIssue } from "types"; +import Tiptap from "components/tiptap"; export interface IssueDescriptionFormValues { name: string; @@ -126,7 +126,6 @@ export const IssueDescriptionForm: FC = ({ setIsSubmitting={setIsSubmitting} onChange={(description: Object, description_html: string) => { onChange(description_html); - // setValue("description_html", description_html); setValue("description", description); handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting(false)); }} diff --git a/apps/app/components/issues/plugins/UploadHelper.tsx b/apps/app/components/issues/plugins/UploadHelper.tsx deleted file mode 100644 index 5a64d4c0a..000000000 --- a/apps/app/components/issues/plugins/UploadHelper.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import fileService from 'services/file.service'; - -const UploadImageHandler = (file: File): Promise => { - try { - const formData = new FormData(); - formData.append("asset", file); - formData.append("attributes", JSON.stringify({})); - - return new Promise(async (resolve, reject) => { - const imageUrl = await fileService - .uploadFile("plane", formData) - .then((response) => response.asset); - - console.log(imageUrl, "imageurl") - const image = new Image(); - image.src = imageUrl; - image.onload = () => { - resolve(imageUrl); - }; - }) - } - catch (error) { - console.log(error) - return Promise.reject(error); - } -}; - -export default UploadImageHandler; diff --git a/apps/app/components/issues/tiptap.tsx b/apps/app/components/issues/tiptap.tsx deleted file mode 100644 index ddadc9d13..000000000 --- a/apps/app/components/issues/tiptap.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useEditor, EditorContent } from '@tiptap/react'; -import { useDebouncedCallback } from 'use-debounce'; -import { EditorBubbleMenu } from './EditorBubbleMenu'; -import { TiptapExtensions } from './extensions'; -import { TiptapEditorProps } from './props'; - -type TiptapProps = { - value: string; - noBorder?: boolean; - borderOnFocus?: boolean; - customClassName?: string; - onChange?: (json: any, html: string) => void; - setIsSubmitting: (isSubmitting: boolean) => void; -} - -const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { - const editor = useEditor({ - editorProps: TiptapEditorProps, - extensions: TiptapExtensions, - content: value, - onUpdate: async ({ editor }) => { - setIsSubmitting(true); - debouncedUpdates({ onChange, editor }); - } - }); - - const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => { - setTimeout(async () => { - if (onChange) { - onChange(editor.getJSON(), editor.getHTML()); - } - }, 500); - }, 1000); - - const editorClassNames = `mt-2 p-3 relative focus:outline-none rounded-md focus:border-custom-border-200 - ${noBorder ? '' : 'border border-custom-border-200' - } ${borderOnFocus ? 'focus:border border-custom-border-200' : 'focus:border-0' - } ${customClassName}`; - - return ( -
{ - editor?.chain().focus().run(); - }} - className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`} - > - {editor && } -
- -
-
- ); -}; - -export default Tiptap; diff --git a/apps/app/components/issues/EditorBubbleMenu.tsx b/apps/app/components/tiptap/bubble-menu/index.tsx similarity index 99% rename from apps/app/components/issues/EditorBubbleMenu.tsx rename to apps/app/components/tiptap/bubble-menu/index.tsx index 32ad3f2a9..00b671393 100644 --- a/apps/app/components/issues/EditorBubbleMenu.tsx +++ b/apps/app/components/tiptap/bubble-menu/index.tsx @@ -10,7 +10,7 @@ import { import { NodeSelector } from "./node-selector"; import { LinkSelector } from "./link-selector"; -import { cn } from "./utils"; +import { cn } from "../utils" export interface BubbleMenuItem { name: string; diff --git a/apps/app/components/issues/link-selector.tsx b/apps/app/components/tiptap/bubble-menu/link-selector.tsx similarity index 98% rename from apps/app/components/issues/link-selector.tsx rename to apps/app/components/tiptap/bubble-menu/link-selector.tsx index 7649c271c..62331ebee 100644 --- a/apps/app/components/issues/link-selector.tsx +++ b/apps/app/components/tiptap/bubble-menu/link-selector.tsx @@ -1,7 +1,7 @@ import { Editor } from "@tiptap/core"; import { Check, Trash } from "lucide-react"; import { Dispatch, FC, SetStateAction, useEffect, useRef } from "react"; -import { cn } from './utils'; +import { cn } from '../utils'; interface LinkSelectorProps { editor: Editor; diff --git a/apps/app/components/issues/node-selector.tsx b/apps/app/components/tiptap/bubble-menu/node-selector.tsx similarity index 97% rename from apps/app/components/issues/node-selector.tsx rename to apps/app/components/tiptap/bubble-menu/node-selector.tsx index ebb9767ec..e74bfa400 100644 --- a/apps/app/components/issues/node-selector.tsx +++ b/apps/app/components/tiptap/bubble-menu/node-selector.tsx @@ -13,8 +13,8 @@ import { } from "lucide-react"; import { Dispatch, FC, SetStateAction } from "react"; -import { BubbleMenuItem } from "./EditorBubbleMenu"; -import { cn } from "./utils"; +import { BubbleMenuItem } from "../bubble-menu"; +import { cn } from "../utils"; interface NodeSelectorProps { editor: Editor; diff --git a/apps/app/components/issues/extensions.tsx b/apps/app/components/tiptap/extensions/index.tsx similarity index 93% rename from apps/app/components/issues/extensions.tsx rename to apps/app/components/tiptap/extensions/index.tsx index f34d32fe4..81a6aa6eb 100644 --- a/apps/app/components/issues/extensions.tsx +++ b/apps/app/components/tiptap/extensions/index.tsx @@ -12,13 +12,14 @@ import { Markdown } from "tiptap-markdown"; import Highlight from "@tiptap/extension-highlight"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { lowlight } from 'lowlight/lib/core' -import SlashCommand from "./slash-command"; +import SlashCommand from "../slash-command"; import { InputRule } from "@tiptap/core"; import ts from 'highlight.js/lib/languages/typescript' import 'highlight.js/styles/github-dark.css'; -import UploadImagesPlugin from "./plugins/upload-image"; +import UploadImagesPlugin from "../plugins/upload-image"; +import UniqueID from "@tiptap-pro/extension-unique-id"; lowlight.registerLanguage('ts', ts) @@ -115,6 +116,9 @@ export const TiptapExtensions = [ }, includeChildren: true, }), + UniqueID.configure({ + types: ['heading', 'paragraph', 'image'], + }), SlashCommand, TiptapUnderline, TextStyle, diff --git a/apps/app/components/tiptap/index.tsx b/apps/app/components/tiptap/index.tsx new file mode 100644 index 000000000..d0365769d --- /dev/null +++ b/apps/app/components/tiptap/index.tsx @@ -0,0 +1,110 @@ +import { useEditor, EditorContent } from '@tiptap/react'; +import { useDebouncedCallback } from 'use-debounce'; +import { EditorBubbleMenu } from './bubble-menu'; +import { TiptapExtensions } from './extensions'; +import { TiptapEditorProps } from './props'; +import { Node } from "@tiptap/pm/model"; +import { Editor as CoreEditor } from "@tiptap/core"; +import { useCallback, useRef } from 'react'; +import { EditorState } from '@tiptap/pm/state'; +import fileService from 'services/file.service'; + +type TiptapProps = { + value: string; + noBorder?: boolean; + borderOnFocus?: boolean; + customClassName?: string; + onChange?: (json: any, html: string) => void; + setIsSubmitting: (isSubmitting: boolean) => void; +} + +const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { + const editor = useEditor({ + editorProps: TiptapEditorProps, + extensions: TiptapExtensions, + content: value, + onUpdate: async ({ editor }) => { + setIsSubmitting(true); + checkForNodeDeletions(editor) + debouncedUpdates({ onChange, editor }); + } + }); + + const previousState = useRef(); + + const extractPath = useCallback((url: string, searchString: string) => { + if (url.startsWith(searchString)) { + console.log("chala", url, searchString) + return url.substring(searchString.length); + } + }, []); + + const onNodeDeleted = useCallback( + async (node: Node) => { + if (node.type.name === 'image') { + const assetUrlWithWorkspaceId = new URL(node.attrs.src).pathname.substring(1); + const resStatus = await fileService.deleteFile(assetUrlWithWorkspaceId); + if (resStatus === 204) { + console.log("file deleted successfully"); + } + } + }, + [], + ); + + const checkForNodeDeletions = useCallback( + (editor: CoreEditor) => { + const prevNodesById: Record = {}; + previousState.current?.doc.forEach((node) => { + if (node.attrs.id) { + prevNodesById[node.attrs.id] = node; + } + }); + + const nodesById: Record = {}; + editor.state?.doc.forEach((node) => { + if (node.attrs.id) { + nodesById[node.attrs.id] = node; + } + }); + + previousState.current = editor.state; + + for (const [id, node] of Object.entries(prevNodesById)) { + if (nodesById[id] === undefined) { + onNodeDeleted(node); + } + } + }, + [onNodeDeleted], + ); + + const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => { + setTimeout(async () => { + if (onChange) { + onChange(editor.getJSON(), editor.getHTML()); + } + }, 500); + }, 1000); + + const editorClassNames = `mt-2 p-3 relative focus:outline-none rounded-md focus:border-custom-border-200 + ${noBorder ? '' : 'border border-custom-border-200' + } ${borderOnFocus ? 'focus:border border-custom-border-200' : 'focus:border-0' + } ${customClassName}`; + + return ( +
{ + editor?.chain().focus().run(); + }} + className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`} + > + {editor && } +
+ +
+
+ ); +}; + +export default Tiptap; diff --git a/apps/app/components/issues/plugins/upload-image.tsx b/apps/app/components/tiptap/plugins/upload-image.tsx similarity index 99% rename from apps/app/components/issues/plugins/upload-image.tsx rename to apps/app/components/tiptap/plugins/upload-image.tsx index b1ea94cc2..d92aa38aa 100644 --- a/apps/app/components/issues/plugins/upload-image.tsx +++ b/apps/app/components/tiptap/plugins/upload-image.tsx @@ -42,7 +42,7 @@ const UploadImagesPlugin = () => props: { decorations(state) { return this.getState(state); - }, + } }, }); diff --git a/apps/app/components/issues/props.ts b/apps/app/components/tiptap/props.tsx similarity index 100% rename from apps/app/components/issues/props.ts rename to apps/app/components/tiptap/props.tsx diff --git a/apps/app/components/issues/slash-command.tsx b/apps/app/components/tiptap/slash-command/index.tsx similarity index 92% rename from apps/app/components/issues/slash-command.tsx rename to apps/app/components/tiptap/slash-command/index.tsx index d869001cd..ab93945a9 100644 --- a/apps/app/components/issues/slash-command.tsx +++ b/apps/app/components/tiptap/slash-command/index.tsx @@ -21,7 +21,9 @@ import { Code, MinusSquare, CheckSquare, + ImageIcon, } from "lucide-react"; +import { startImageUpload } from "../plugins/upload-image"; interface CommandItemProps { title: string; @@ -180,6 +182,27 @@ const getSuggestionItems = ({ query }: { query: string }) => 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); + } + }; + input.click(); + }, + }, ].filter((item) => { if (typeof query === "string" && query.length > 0) { const search = query.toLowerCase(); diff --git a/apps/app/components/issues/utils.ts b/apps/app/components/tiptap/utils.ts similarity index 100% rename from apps/app/components/issues/utils.ts rename to apps/app/components/tiptap/utils.ts diff --git a/apps/app/package.json b/apps/app/package.json index 77860d7d0..d9911841c 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -27,6 +27,7 @@ "@nivo/scatterplot": "0.80.0", "@sentry/nextjs": "^7.36.0", "@tailwindcss/typography": "^0.5.9", + "@tiptap-pro/extension-unique-id": "^2.1.0", "@tiptap/extension-code-block-lowlight": "^2.0.4", "@tiptap/extension-color": "^2.0.4", "@tiptap/extension-highlight": "^2.0.4", diff --git a/apps/app/services/file.service.ts b/apps/app/services/file.service.ts index ad87e3a19..a80b2ce80 100644 --- a/apps/app/services/file.service.ts +++ b/apps/app/services/file.service.ts @@ -40,12 +40,9 @@ class FileServices extends APIService { }); } - async deleteFile(workspaceId: string, assetUrl: string): Promise { - const lastIndex = assetUrl.lastIndexOf("/"); - const assetId = assetUrl.substring(lastIndex + 1); - - return this.delete(`/api/workspaces/file-assets/${workspaceId}/${assetId}/`) - .then((response) => response?.data) + async deleteFile(assetUrlWithWorkspaceId: string): Promise { + return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`) + .then((response) => response?.status) .catch((error) => { throw error?.response?.data; }); From 5c290e1302bfd3d63b824c7ccee475cf4a88d6fa Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:42:57 +0530 Subject: [PATCH 11/27] Integrated tiptap with Issue Modal --- apps/app/components/issues/form.tsx | 52 +++++++++++-------- .../components/tiptap/extensions/index.tsx | 6 +-- .../tiptap/hooks/useDebouncedUpdates.tsx | 18 +++++++ .../tiptap/hooks/useNodeDeletion.tsx | 51 ++++++++++++++++++ apps/app/components/tiptap/index.tsx | 13 ++--- 5 files changed, 104 insertions(+), 36 deletions(-) create mode 100644 apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx create mode 100644 apps/app/components/tiptap/hooks/useNodeDeletion.tsx diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index e16bd317f..8d66edd21 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -36,6 +36,7 @@ import { import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types import type { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types"; +import Tiptap from "components/tiptap"; // rich-text-editor // const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { // ssr: false, @@ -145,6 +146,8 @@ export const IssueForm: FC = ({ reValidateMode: "onChange", }); + console.log("values", getValues()); + const issueName = watch("name"); const handleCreateUpdateIssue = async (formData: Partial) => { @@ -338,9 +341,8 @@ export const IssueForm: FC = ({ {issueName && issueName !== "" && ( - {/* ( */} - {/* setValue("description", jsonValue)} */} - {/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */} - {/* placeholder="Description" */} - {/* ref={editorRef} */} - {/* /> */} - {/* )} */} - {/* /> */} + { + if (!value && !watch("description_html")) return <>; + + return ( + { + onChange(description_html); + setValue("description", description); + }} + /> + ); + }} + /> { @@ -523,7 +529,7 @@ export const IssueForm: FC = ({ onClick={() => setCreateMore((prevData) => !prevData)} > Create more - {}} size="md" /> + { }} size="md" />
Discard @@ -533,8 +539,8 @@ export const IssueForm: FC = ({ ? "Updating Issue..." : "Update Issue" : isSubmitting - ? "Adding Issue..." - : "Add Issue"} + ? "Adding Issue..." + : "Add Issue"}
diff --git a/apps/app/components/tiptap/extensions/index.tsx b/apps/app/components/tiptap/extensions/index.tsx index 81a6aa6eb..14d7106eb 100644 --- a/apps/app/components/tiptap/extensions/index.tsx +++ b/apps/app/components/tiptap/extensions/index.tsx @@ -54,7 +54,7 @@ export const TiptapExtensions = [ code: { HTMLAttributes: { class: - "rounded-md bg-stone-200 px-1 py-1 font-mono font-medium text-stone-900", + "rounded-md bg-custom-bg-1000 px-1 py-1 font-mono font-medium text-stone-900", spellcheck: "false", }, }, @@ -62,7 +62,7 @@ export const TiptapExtensions = [ horizontalRule: false, dropcursor: { color: "#DBEAFE", - width: 4, + width: 2, }, gapcursor: false, }), @@ -117,7 +117,7 @@ export const TiptapExtensions = [ includeChildren: true, }), UniqueID.configure({ - types: ['heading', 'paragraph', 'image'], + types: ['image'], }), SlashCommand, TiptapUnderline, diff --git a/apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx b/apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx new file mode 100644 index 000000000..0fc835a98 --- /dev/null +++ b/apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx @@ -0,0 +1,18 @@ +import { useDebouncedCallback } from 'use-debounce'; +import { Editor as CoreEditor } from "@tiptap/core"; + +type DebouncedUpdatesProps = { + onChange?: (json: any, html: string) => void; + editor: CoreEditor | null; +}; + +export const useDebouncedUpdates = (props: DebouncedUpdatesProps) => + useDebouncedCallback(async () => { + setTimeout(async () => { + if (props.onChange) { + props.onChange(props.editor.getJSON(), props.editor.getHTML()); + } + }, 500); + }, 1000); +; + diff --git a/apps/app/components/tiptap/hooks/useNodeDeletion.tsx b/apps/app/components/tiptap/hooks/useNodeDeletion.tsx new file mode 100644 index 000000000..9370947b6 --- /dev/null +++ b/apps/app/components/tiptap/hooks/useNodeDeletion.tsx @@ -0,0 +1,51 @@ +import { useCallback, useRef } from 'react'; +import { Node } from "@tiptap/pm/model"; +import { Editor as CoreEditor } from "@tiptap/core"; +import { EditorState } from '@tiptap/pm/state'; +import fileService from 'services/file.service'; + +export const useNodeDeletion = () => { + const previousState = useRef(); + + const onNodeDeleted = useCallback( + async (node: Node) => { + if (node.type.name === 'image') { + const assetUrlWithWorkspaceId = new URL(node.attrs.src).pathname.substring(1); + const resStatus = await fileService.deleteFile(assetUrlWithWorkspaceId); + if (resStatus === 204) { + console.log("file deleted successfully"); + } + } + }, + [], + ); + + const checkForNodeDeletions = useCallback( + (editor: CoreEditor) => { + const prevNodesById: Record = {}; + previousState.current?.doc.forEach((node) => { + if (node.attrs.id) { + prevNodesById[node.attrs.id] = node; + } + }); + + const nodesById: Record = {}; + editor.state?.doc.forEach((node) => { + if (node.attrs.id) { + nodesById[node.attrs.id] = node; + } + }); + + previousState.current = editor.state; + + for (const [id, node] of Object.entries(prevNodesById)) { + if (nodesById[id] === undefined) { + onNodeDeleted(node); + } + } + }, + [onNodeDeleted], + ); + + return { checkForNodeDeletions }; +}; diff --git a/apps/app/components/tiptap/index.tsx b/apps/app/components/tiptap/index.tsx index d0365769d..d2103d506 100644 --- a/apps/app/components/tiptap/index.tsx +++ b/apps/app/components/tiptap/index.tsx @@ -15,7 +15,7 @@ type TiptapProps = { borderOnFocus?: boolean; customClassName?: string; onChange?: (json: any, html: string) => void; - setIsSubmitting: (isSubmitting: boolean) => void; + setIsSubmitting?: (isSubmitting: boolean) => void; } const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { @@ -24,7 +24,7 @@ const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, cus extensions: TiptapExtensions, content: value, onUpdate: async ({ editor }) => { - setIsSubmitting(true); + setIsSubmitting?.(true); checkForNodeDeletions(editor) debouncedUpdates({ onChange, editor }); } @@ -32,13 +32,6 @@ const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, cus const previousState = useRef(); - const extractPath = useCallback((url: string, searchString: string) => { - if (url.startsWith(searchString)) { - console.log("chala", url, searchString) - return url.substring(searchString.length); - } - }, []); - const onNodeDeleted = useCallback( async (node: Node) => { if (node.type.name === 'image') { @@ -100,7 +93,7 @@ const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, cus className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`} > {editor && } -
+
From 0b6d510cc7aeae9e318a4d7157ecb75715762e9a Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:20:15 +0530 Subject: [PATCH 12/27] added additional props and Tiptap Integration with Comments --- .../components/issues/comment/add-comment.tsx | 47 +++++++++++------- .../issues/comment/comment-card.tsx | 47 +++++++++++++----- .../components/issues/description-form.tsx | 4 ++ apps/app/components/issues/form.tsx | 1 + apps/app/components/issues/main-content.tsx | 48 +++++++++---------- 5 files changed, 96 insertions(+), 51 deletions(-) diff --git a/apps/app/components/issues/comment/add-comment.tsx b/apps/app/components/issues/comment/add-comment.tsx index 3b3cd21b0..157f56dce 100644 --- a/apps/app/components/issues/comment/add-comment.tsx +++ b/apps/app/components/issues/comment/add-comment.tsx @@ -1,7 +1,6 @@ import React from "react"; import { useRouter } from "next/router"; -import dynamic from "next/dynamic"; import { mutate } from "swr"; @@ -12,11 +11,12 @@ import issuesServices from "services/issues.service"; // hooks import useToast from "hooks/use-toast"; // ui -import { Loader, SecondaryButton } from "components/ui"; +import { SecondaryButton } from "components/ui"; // types import type { ICurrentUserResponse, IIssueComment } from "types"; // fetch-keys import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; +import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; // const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { // ssr: false, @@ -34,7 +34,14 @@ import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; // >((props, ref) => ); // // WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; -// + +const TiptapEditor = React.forwardRef< + ITiptapRichTextEditor, + ITiptapRichTextEditor +>((props, ref) => ); + +TiptapEditor.displayName = "TiptapEditor"; + const defaultValues: Partial = { comment_json: "", comment_html: "", @@ -51,6 +58,7 @@ export const AddComment: React.FC = ({ issueId, user, disabled = false }) handleSubmit, control, setValue, + watch, formState: { isSubmitting }, reset, } = useForm({ defaultValues }); @@ -98,19 +106,26 @@ export const AddComment: React.FC = ({ issueId, user, disabled = false })
- {/* ( */} - {/* setValue("comment_json", jsonValue)} */} - {/* onHTMLChange={(htmlValue) => setValue("comment_html", htmlValue)} */} - {/* placeholder="Enter your comment..." */} - {/* ref={editorRef} */} - {/* /> */} - {/* )} */} - {/* /> */} + + { + onChange(comment_html); + setValue("comment_json", comment_json); + }} + /> + } + /> {isSubmitting ? "Adding..." : "Comment"} diff --git a/apps/app/components/issues/comment/comment-card.tsx b/apps/app/components/issues/comment/comment-card.tsx index 008290f43..10da0b74e 100644 --- a/apps/app/components/issues/comment/comment-card.tsx +++ b/apps/app/components/issues/comment/comment-card.tsx @@ -15,6 +15,7 @@ import { CommentReaction } from "components/issues"; import { timeAgo } from "helpers/date-time.helper"; // types import type { IIssueComment } from "types"; +import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; // const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false }); // @@ -26,7 +27,14 @@ import type { IIssueComment } from "types"; // >((props, ref) => ); // // WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; -// + +const TiptapEditor = React.forwardRef< + ITiptapRichTextEditor, + ITiptapRichTextEditor +>((props, ref) => ); + +TiptapEditor.displayName = "TiptapEditor"; + type Props = { comment: IIssueComment; onSubmit: (comment: IIssueComment) => void; @@ -45,6 +53,7 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD formState: { isSubmitting }, handleSubmit, setFocus, + watch, setValue, } = useForm({ defaultValues: comment, @@ -55,9 +64,10 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD setIsEditing(false); onSubmit(formData); + console.log("watching", formData.comment_html) - editorRef.current?.setEditorValue(formData.comment_json); - showEditorRef.current?.setEditorValue(formData.comment_json); + editorRef.current?.setEditorValue(formData.comment_html); + showEditorRef.current?.setEditorValue(formData.comment_html); }; useEffect(() => { @@ -105,14 +115,24 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`} onSubmit={handleSubmit(onEnter)} > - { - setValue("comment_json", jsonValue); - setValue("comment_html", htmlValue); - }} - placeholder="Enter Your comment..." + {/* { */} + {/* setValue("comment_json", jsonValue); */} + {/* setValue("comment_html", htmlValue); */} + {/* }} */} + {/* placeholder="Enter Your comment..." */} + {/* ref={editorRef} */} + {/* /> */} + { + setValue("comment_json", comment_json); + setValue("comment_html", comment_html); + }} />
+ {/* = ({ comment, onSubmit, handleCommentD {/* customClassName="text-xs border border-custom-border-200 bg-custom-background-100" */} {/* ref={showEditorRef} */} {/* /> */} -
diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index fcf10073d..1eb02915f 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -123,8 +123,12 @@ export const IssueDescriptionForm: FC = ({ ? watch("description_html") : value } + debouncedUpdatesEnabled={true} setIsSubmitting={setIsSubmitting} + customClassName="min-h-[150px]" + editorContentCustomClassNames="pt-9" onChange={(description: Object, description_html: string) => { + setIsSubmitting(true); onChange(description_html); setValue("description", description); handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting(false)); diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 8d66edd21..8f71c8e53 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -372,6 +372,7 @@ export const IssueForm: FC = ({ return ( = ({ isAllowed={memberRole.isMember || memberRole.isOwner || !uneditable} /> - {/* */} - {/**/} - {/*
*/} - {/* */} - {/*
*/} - {/*
*/} - {/*
*/} - {/*

Attachments

*/} - {/*
*/} - {/* */} - {/* */} - {/*
*/} - {/*
*/} - {/*
*/} - {/*

Comments/Activity

*/} - {/* */} - {/* */} + + +
+ +
+
+
+

Attachments

+
+ + +
+
+
+

Comments/Activity

+ +
); From b1dc5f3da16f1c1cb4286e694c2bd14493fa3c42 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:20:40 +0530 Subject: [PATCH 13/27] added tiptap integration with user activity feeds --- apps/app/components/core/feeds.tsx | 38 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/apps/app/components/core/feeds.tsx b/apps/app/components/core/feeds.tsx index 8144bfc97..d4781398d 100644 --- a/apps/app/components/core/feeds.tsx +++ b/apps/app/components/core/feeds.tsx @@ -15,6 +15,7 @@ import { Icon } from "components/ui"; // helpers import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper"; +import Tiptap from "components/tiptap"; // types // import RemirrorRichTextEditor from "components/rich-text-editor"; @@ -200,7 +201,7 @@ export const Feeds: React.FC = ({ activities }) => { } else if (activity.field === "estimate_point") { value = activity.new_value ? activity.new_value + - ` Point${parseInt(activity.new_value ?? "", 10) > 1 ? "s" : ""}` + ` Point${parseInt(activity.new_value ?? "", 10) > 1 ? "s" : ""}` : "None"; } @@ -250,18 +251,29 @@ export const Feeds: React.FC = ({ activities }) => { Commented {timeAgo(activity.created_at)}

- {/*
*/} - {/* */} - {/*
*/} +
+ + {/**/} + {/* */} +
From 95358503ed3ede622839f209d34ee59252e031e6 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:21:15 +0530 Subject: [PATCH 14/27] added ref control support and bubble menu support for readonly editor --- .../components/tiptap/bubble-menu/index.tsx | 3 ++ apps/app/components/tiptap/index.tsx | 41 +++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/apps/app/components/tiptap/bubble-menu/index.tsx b/apps/app/components/tiptap/bubble-menu/index.tsx index 00b671393..a70481b88 100644 --- a/apps/app/components/tiptap/bubble-menu/index.tsx +++ b/apps/app/components/tiptap/bubble-menu/index.tsx @@ -58,6 +58,9 @@ export const EditorBubbleMenu: FC = (props) => { const bubbleMenuProps: EditorBubbleMenuProps = { ...props, shouldShow: ({ editor }) => { + if (!editor.isEditable) { + return false; + } if (editor.isActive("image")) { return false; } diff --git a/apps/app/components/tiptap/index.tsx b/apps/app/components/tiptap/index.tsx index d2103d506..ddf0fd4cd 100644 --- a/apps/app/components/tiptap/index.tsx +++ b/apps/app/components/tiptap/index.tsx @@ -1,35 +1,59 @@ -import { useEditor, EditorContent } from '@tiptap/react'; +import { useEditor, EditorContent, Editor } from '@tiptap/react'; import { useDebouncedCallback } from 'use-debounce'; import { EditorBubbleMenu } from './bubble-menu'; import { TiptapExtensions } from './extensions'; import { TiptapEditorProps } from './props'; import { Node } from "@tiptap/pm/model"; import { Editor as CoreEditor } from "@tiptap/core"; -import { useCallback, useRef } from 'react'; +import { useCallback, useImperativeHandle, useRef } from 'react'; import { EditorState } from '@tiptap/pm/state'; import fileService from 'services/file.service'; -type TiptapProps = { +export interface ITiptapRichTextEditor { value: string; noBorder?: boolean; borderOnFocus?: boolean; customClassName?: string; + editorContentCustomClassNames?: string; onChange?: (json: any, html: string) => void; setIsSubmitting?: (isSubmitting: boolean) => void; + editable?: boolean; + forwardedRef?: any; + debouncedUpdatesEnabled?: boolean; } -const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { +const Tiptap = ({ onChange, debouncedUpdatesEnabled, forwardedRef, editable, setIsSubmitting, editorContentCustomClassNames, value, noBorder, borderOnFocus, customClassName }: ITiptapRichTextEditor) => { const editor = useEditor({ + editable: editable ?? true, editorProps: TiptapEditorProps, extensions: TiptapExtensions, content: value, onUpdate: async ({ editor }) => { + // for instant feedback loop setIsSubmitting?.(true); checkForNodeDeletions(editor) - debouncedUpdates({ onChange, editor }); + if (debouncedUpdatesEnabled) { + debouncedUpdates({ onChange, editor }); + } else { + onChange?.(editor.getJSON(), editor.getHTML()); + } } }); + const editorRef: React.MutableRefObject = useRef(null) + + useImperativeHandle(forwardedRef, () => ({ + clearEditor: () => { + console.log('clearContent') + console.log(editorRef) + editorRef.current?.commands.clearContent() + }, + setEditorValue: (content: string) => { + console.log(editorRef, forwardedRef, content) + editorRef.current?.commands.setContent(content) + } + })) + const previousState = useRef(); const onNodeDeleted = useCallback( @@ -85,15 +109,18 @@ const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, cus } ${borderOnFocus ? 'focus:border border-custom-border-200' : 'focus:border-0' } ${customClassName}`; + if (!editor) return null + editorRef.current = editor + return (
{ editor?.chain().focus().run(); }} - className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`} + className={`tiptap-editor-container relative ${editorClassNames}`} > {editor && } -
+
From c2a4cdfebb361267a316f46927c32a278057cde9 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:21:36 +0530 Subject: [PATCH 15/27] added tiptap support for plane pages --- .../pages/create-update-block-inline.tsx | 143 +++++++++++------- .../components/pages/single-page-block.tsx | 49 +++--- 2 files changed, 116 insertions(+), 76 deletions(-) diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index 3b73e041b..620d47241 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -23,6 +23,7 @@ import { Loader, PrimaryButton, SecondaryButton, TextArea } from "components/ui" import { ICurrentUserResponse, IPageBlock } from "types"; // fetch-keys import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; +import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; type Props = { handleClose: () => void; @@ -56,6 +57,13 @@ const defaultValues = { // // WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +const TiptapEditor = React.forwardRef< + ITiptapRichTextEditor, + ITiptapRichTextEditor +>((props, ref) => ); + +TiptapEditor.displayName = "TiptapEditor"; + export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, @@ -242,9 +250,9 @@ export const CreateUpdateBlockInline: React.FC = ({ description: !data.description || data.description === "" ? { - type: "doc", - content: [{ type: "paragraph" }], - } + type: "doc", + content: [{ type: "paragraph" }], + } : data.description, description_html: data.description_html ?? "

", }); @@ -296,57 +304,86 @@ export const CreateUpdateBlockInline: React.FC = ({ />
- {/* { */} - {/* if (!data) */} - {/* return ( */} - {/* setValue("description", jsonValue)} */} - {/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */} - {/* placeholder="Write something..." */} - {/* customClassName="text-sm" */} - {/* noBorder */} - {/* borderOnFocus={false} */} - {/* ref={editorRef} */} - {/* /> */} - {/* ); */} - {/* else if (!value || !watch("description_html")) */} - {/* return ( */} - {/*
*/} - {/* ); */} - {/**/} - {/* return ( */} - {/* 0 */} - {/* ? value */} - {/* : watch("description_html") && watch("description_html") !== "" */} - {/* ? watch("description_html") */} - {/* : { type: "doc", content: [{ type: "paragraph" }] } */} - {/* } */} - {/* onJSONChange={(jsonValue) => setValue("description", jsonValue)} */} - {/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */} - {/* placeholder="Write something..." */} - {/* customClassName="text-sm" */} - {/* noBorder */} - {/* borderOnFocus={false} */} - {/* ref={editorRef} */} - {/* /> */} - {/* ); */} - {/* }} */} - {/* /> */} + { + if (!data) + return ( +

"} + debouncedUpdatesEnabled={false} + customClassName="text-sm" + noBorder + borderOnFocus={false} + onChange={(description: Object, description_html: string) => { + onChange(description_html); + setValue("description", description); + }} + /> + // setValue("description", jsonValue)} + // onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} + // placeholder="Write something..." + // customClassName="text-sm" + // noBorder + // borderOnFocus={false} + // ref={editorRef} + // /> + ); + else if (!value || !watch("description_html")) + return ( +
+ ); + + return ( + 0 + ? value + : watch("description_html") && watch("description_html") !== "" + ? watch("description_html") + : { type: "doc", content: [{ type: "paragraph" }] } + } + debouncedUpdatesEnabled={false} + customClassName="text-sm" + noBorder + borderOnFocus={false} + onChange={(description: Object, description_html: string) => { + onChange(description_html); + setValue("description", description); + }} + /> + // 0 + // ? value + // : watch("description_html") && watch("description_html") !== "" + // ? watch("description_html") + // : { type: "doc", content: [{ type: "paragraph" }] } + // } + // onJSONChange={(jsonValue) => setValue("description", jsonValue)} + // onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} + // placeholder="Write something..." + // customClassName="text-sm" + // noBorder + // borderOnFocus={false} + // ref={editorRef} + // /> + ); + }} + />
diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index e9ceb5fab..6ecf98ba5 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -39,6 +39,7 @@ import { copyTextToClipboard } from "helpers/string.helper"; import { ICurrentUserResponse, IIssue, IPageBlock, IProject } from "types"; // fetch-keys import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; +import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; type Props = { block: IPageBlock; @@ -54,6 +55,12 @@ type Props = { // >((props, ref) => ); // WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +const TiptapEditor = React.forwardRef< + ITiptapRichTextEditor, + ITiptapRichTextEditor +>((props, ref) => ); + +TiptapEditor.displayName = "TiptapEditor"; export const SinglePageBlock: React.FC = ({ block, @@ -328,9 +335,8 @@ export const SinglePageBlock: React.FC = ({
) : (
@@ -344,9 +350,8 @@ export const SinglePageBlock: React.FC = ({
Date: Thu, 10 Aug 2023 01:22:07 +0530 Subject: [PATCH 16/27] added tiptap support to gpt assistant modal (yet to be tested) --- .../core/modals/gpt-assistant-modal.tsx | 73 ++++++++++++------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/apps/app/components/core/modals/gpt-assistant-modal.tsx b/apps/app/components/core/modals/gpt-assistant-modal.tsx index 1d3af7a51..0c5e0eded 100644 --- a/apps/app/components/core/modals/gpt-assistant-modal.tsx +++ b/apps/app/components/core/modals/gpt-assistant-modal.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, forwardRef, useRef } from "react"; +import React, { useEffect, useState, forwardRef, useRef } from "react"; import { useRouter } from "next/router"; import dynamic from "next/dynamic"; @@ -15,6 +15,7 @@ import useUserAuth from "hooks/use-user-auth"; import { Input, PrimaryButton, SecondaryButton } from "components/ui"; import { IIssue, IPageBlock } from "types"; +import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; type Props = { isOpen: boolean; handleClose: () => void; @@ -44,6 +45,14 @@ type FormData = { // // WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +const TiptapEditor = React.forwardRef< + ITiptapRichTextEditor, + ITiptapRichTextEditor +>((props, ref) => ); + +TiptapEditor.displayName = "TiptapEditor"; + + export const GptAssistantModal: React.FC = ({ isOpen, handleClose, @@ -146,33 +155,48 @@ export const GptAssistantModal: React.FC = ({ return (
- {/* {((content && content !== "") || (htmlContent && htmlContent !== "

")) && ( */} - {/*
*/} - {/* Content: */} - {/* {content}

} */} - {/* customClassName="-m-3" */} - {/* noBorder */} - {/* borderOnFocus={false} */} - {/* editable={false} */} - {/* ref={editorRef} */} - {/* /> */} - {/*
*/} - {/* )} */} + {((content && content !== "") || (htmlContent && htmlContent !== "

")) && ( +
+ Content: + ${content}

`} + customClassName="-m-3" + noBorder + borderOnFocus={false} + editable={false} + ref={editorRef} + /> + {/* {content}

} */} + {/* customClassName="-m-3" */} + {/* noBorder */} + {/* borderOnFocus={false} */} + {/* editable={false} */} + {/* ref={editorRef} */} + {/* /> */} +
+ )} {response !== "" && (
Response: - ${response}

`} customClassName="-mx-3 -my-3" noBorder borderOnFocus={false} editable={false} /> + + {/* ${response}

`} */} + {/* customClassName="-mx-3 -my-3" */} + {/* noBorder */} + {/* borderOnFocus={false} */} + {/* editable={false} */} + {/* /> */}
)} {invalidResponse && ( @@ -185,11 +209,10 @@ export const GptAssistantModal: React.FC = ({ type="text" name="task" register={register} - placeholder={`${ - content && content !== "" - ? "Tell AI what action to perform on this content..." - : "Ask AI anything..." - }`} + placeholder={`${content && content !== "" + ? "Tell AI what action to perform on this content..." + : "Ask AI anything..." + }`} autoComplete="off" />
@@ -225,8 +248,8 @@ export const GptAssistantModal: React.FC = ({ {isSubmitting ? "Generating response..." : response === "" - ? "Generate response" - : "Generate again"} + ? "Generate response" + : "Generate again"}
From 96ef0a1e4f4b9374c4cb24154fe96b16e1c930f5 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:28:40 +0530 Subject: [PATCH 17/27] removed remirror instances and cleaned up code --- apps/app/components/core/feeds.tsx | 13 ----- .../core/modals/gpt-assistant-modal.tsx | 30 ------------ .../components/issues/comment/add-comment.tsx | 17 ------- .../issues/comment/comment-card.tsx | 29 ----------- apps/app/components/issues/form.tsx | 20 +------- .../pages/create-update-block-inline.tsx | 49 +------------------ apps/app/components/pages/page-form.tsx | 12 ----- .../components/pages/single-page-block.tsx | 7 --- 8 files changed, 2 insertions(+), 175 deletions(-) diff --git a/apps/app/components/core/feeds.tsx b/apps/app/components/core/feeds.tsx index d4781398d..92f6e634f 100644 --- a/apps/app/components/core/feeds.tsx +++ b/apps/app/components/core/feeds.tsx @@ -16,8 +16,6 @@ import { Icon } from "components/ui"; import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper"; import Tiptap from "components/tiptap"; -// types -// import RemirrorRichTextEditor from "components/rich-text-editor"; const activityDetails: { [key: string]: { @@ -262,17 +260,6 @@ export const Feeds: React.FC = ({ activities }) => { noBorder customClassName="text-xs border border-custom-border-200 bg-custom-background-100" /> - {/**/} - {/* */}
diff --git a/apps/app/components/core/modals/gpt-assistant-modal.tsx b/apps/app/components/core/modals/gpt-assistant-modal.tsx index 0c5e0eded..b9bf09ace 100644 --- a/apps/app/components/core/modals/gpt-assistant-modal.tsx +++ b/apps/app/components/core/modals/gpt-assistant-modal.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState, forwardRef, useRef } from "react"; import { useRouter } from "next/router"; -import dynamic from "next/dynamic"; // react-hook-form import { useForm } from "react-hook-form"; @@ -33,18 +32,6 @@ type FormData = { task: string; }; -// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { -// ssr: false, -// }); -// -// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; -// -// const WrappedRemirrorRichTextEditor = forwardRef( -// (props, ref) => -// ); -// -// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; - const TiptapEditor = React.forwardRef< ITiptapRichTextEditor, ITiptapRichTextEditor @@ -52,7 +39,6 @@ const TiptapEditor = React.forwardRef< TiptapEditor.displayName = "TiptapEditor"; - export const GptAssistantModal: React.FC = ({ isOpen, handleClose, @@ -169,14 +155,6 @@ export const GptAssistantModal: React.FC = ({ editable={false} ref={editorRef} /> - {/* {content}

} */} - {/* customClassName="-m-3" */} - {/* noBorder */} - {/* borderOnFocus={false} */} - {/* editable={false} */} - {/* ref={editorRef} */} - {/* /> */} )} {response !== "" && ( @@ -189,14 +167,6 @@ export const GptAssistantModal: React.FC = ({ borderOnFocus={false} editable={false} /> - - {/* ${response}

`} */} - {/* customClassName="-mx-3 -my-3" */} - {/* noBorder */} - {/* borderOnFocus={false} */} - {/* editable={false} */} - {/* /> */} )} {invalidResponse && ( diff --git a/apps/app/components/issues/comment/add-comment.tsx b/apps/app/components/issues/comment/add-comment.tsx index 157f56dce..87fb745a7 100644 --- a/apps/app/components/issues/comment/add-comment.tsx +++ b/apps/app/components/issues/comment/add-comment.tsx @@ -18,23 +18,6 @@ import type { ICurrentUserResponse, IIssueComment } from "types"; import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; -// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { -// ssr: false, -// loading: () => ( -// -// -// -// ), -// }); -// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; -// -// const WrappedRemirrorRichTextEditor = React.forwardRef< -// IRemirrorRichTextEditor, -// IRemirrorRichTextEditor -// >((props, ref) => ); -// -// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; - const TiptapEditor = React.forwardRef< ITiptapRichTextEditor, ITiptapRichTextEditor diff --git a/apps/app/components/issues/comment/comment-card.tsx b/apps/app/components/issues/comment/comment-card.tsx index 10da0b74e..f2770cc77 100644 --- a/apps/app/components/issues/comment/comment-card.tsx +++ b/apps/app/components/issues/comment/comment-card.tsx @@ -1,7 +1,5 @@ import React, { useEffect, useState } from "react"; -import dynamic from "next/dynamic"; - // react-hook-form import { useForm } from "react-hook-form"; // icons @@ -17,17 +15,6 @@ import { timeAgo } from "helpers/date-time.helper"; import type { IIssueComment } from "types"; import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; -// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false }); -// -// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; -// -// const WrappedRemirrorRichTextEditor = React.forwardRef< -// IRemirrorRichTextEditor, -// IRemirrorRichTextEditor -// >((props, ref) => ); -// -// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; - const TiptapEditor = React.forwardRef< ITiptapRichTextEditor, ITiptapRichTextEditor @@ -115,15 +102,6 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`} onSubmit={handleSubmit(onEnter)} > - {/* { */} - {/* setValue("comment_json", jsonValue); */} - {/* setValue("comment_html", htmlValue); */} - {/* }} */} - {/* placeholder="Enter Your comment..." */} - {/* ref={editorRef} */} - {/* /> */} = ({ comment, onSubmit, handleCommentD editable={false} customClassName="text-xs border border-custom-border-200 bg-custom-background-100" /> - {/* */} diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 8f71c8e53..c2a32b4e4 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -1,6 +1,5 @@ import React, { FC, useState, useEffect, useRef } from "react"; -import dynamic from "next/dynamic"; import { useRouter } from "next/router"; // react-hook-form @@ -36,25 +35,8 @@ import { import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types import type { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types"; -import Tiptap from "components/tiptap"; // rich-text-editor -// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { -// ssr: false, -// loading: () => ( -// -// -// -// ), -// }); - -// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; - -// const WrappedRemirrorRichTextEditor = React.forwardRef< -// IRemirrorRichTextEditor, -// IRemirrorRichTextEditor -// >((props, ref) => ); -// -// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; +import Tiptap from "components/tiptap"; const defaultValues: Partial = { project: "", diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index 620d47241..da7384921 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/router"; -import dynamic from "next/dynamic"; import { mutate } from "swr"; @@ -18,7 +17,7 @@ import useToast from "hooks/use-toast"; // components import { GptAssistantModal } from "components/core"; // ui -import { Loader, PrimaryButton, SecondaryButton, TextArea } from "components/ui"; +import { PrimaryButton, SecondaryButton, TextArea } from "components/ui"; // types import { ICurrentUserResponse, IPageBlock } from "types"; // fetch-keys @@ -40,23 +39,6 @@ const defaultValues = { description_html: null, }; -// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { -// ssr: false, -// loading: () => ( -// -// -// -// ), -// }); -// import { IRemirrorRichTextEditor } from "components/rich-text-editor"; -// -// const WrappedRemirrorRichTextEditor = React.forwardRef< -// IRemirrorRichTextEditor, -// IRemirrorRichTextEditor -// >((props, ref) => ); -// -// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; - const TiptapEditor = React.forwardRef< ITiptapRichTextEditor, ITiptapRichTextEditor @@ -322,19 +304,6 @@ export const CreateUpdateBlockInline: React.FC = ({ setValue("description", description); }} /> - // setValue("description", jsonValue)} - // onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - // placeholder="Write something..." - // customClassName="text-sm" - // noBorder - // borderOnFocus={false} - // ref={editorRef} - // /> ); else if (!value || !watch("description_html")) return ( @@ -360,22 +329,6 @@ export const CreateUpdateBlockInline: React.FC = ({ setValue("description", description); }} /> - // 0 - // ? value - // : watch("description_html") && watch("description_html") !== "" - // ? watch("description_html") - // : { type: "doc", content: [{ type: "paragraph" }] } - // } - // onJSONChange={(jsonValue) => setValue("description", jsonValue)} - // onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - // placeholder="Write something..." - // customClassName="text-sm" - // noBorder - // borderOnFocus={false} - // ref={editorRef} - // /> ); }} /> diff --git a/apps/app/components/pages/page-form.tsx b/apps/app/components/pages/page-form.tsx index ff435d3e5..c4c669e1e 100644 --- a/apps/app/components/pages/page-form.tsx +++ b/apps/app/components/pages/page-form.tsx @@ -1,7 +1,5 @@ import { useEffect } from "react"; -import dynamic from "next/dynamic"; - // react-hook-form import { useForm } from "react-hook-form"; // ui @@ -16,16 +14,6 @@ type Props = { data?: IPage | null; }; -// rich-text-editor -// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { -// ssr: false, -// loading: () => ( -// -// -// -// ), -// }); - const defaultValues = { name: "", description: "", diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index 6ecf98ba5..8a13e3ea1 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -19,7 +19,6 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector"; // components import { GptAssistantModal } from "components/core"; import { CreateUpdateBlockInline } from "components/pages"; -// import RemirrorRichTextEditor, { IRemirrorRichTextEditor } from "components/rich-text-editor"; // ui import { CustomMenu, TextArea } from "components/ui"; // icons @@ -49,12 +48,6 @@ type Props = { user: ICurrentUserResponse | undefined; }; -// const WrappedRemirrorRichTextEditor = React.forwardRef< -// IRemirrorRichTextEditor, -// IRemirrorRichTextEditor -// >((props, ref) => ); - -// WrappedRemirrorRichTextEditor.displayName = "WrappedRemirrorRichTextEditor"; const TiptapEditor = React.forwardRef< ITiptapRichTextEditor, ITiptapRichTextEditor From 78a77cf5605b1821ad2c79b52774a1b2b47e495f Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 02:50:34 +0530 Subject: [PATCH 18/27] improved code structure for extracting props in Tiptap --- apps/app/components/tiptap/index.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/app/components/tiptap/index.tsx b/apps/app/components/tiptap/index.tsx index ddf0fd4cd..da5e0db7a 100644 --- a/apps/app/components/tiptap/index.tsx +++ b/apps/app/components/tiptap/index.tsx @@ -22,7 +22,20 @@ export interface ITiptapRichTextEditor { debouncedUpdatesEnabled?: boolean; } -const Tiptap = ({ onChange, debouncedUpdatesEnabled, forwardedRef, editable, setIsSubmitting, editorContentCustomClassNames, value, noBorder, borderOnFocus, customClassName }: ITiptapRichTextEditor) => { +const Tiptap = (props: ITiptapRichTextEditor) => { + const { + onChange, + debouncedUpdatesEnabled, + forwardedRef, + editable, + setIsSubmitting, + editorContentCustomClassNames, + value, + noBorder, + borderOnFocus, + customClassName + } = props; + const editor = useEditor({ editable: editable ?? true, editorProps: TiptapEditorProps, From a96514dc3710554e808244220540f60d32d640bc Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 03:20:20 +0530 Subject: [PATCH 19/27] fixing ts errors for next build --- .../app/components/rich-text-editor/index.tsx | 236 ------------- .../rich-text-editor/mention-autocomplete.tsx | 64 ---- .../components/rich-text-editor/sample.tsx | 145 -------- .../toolbar/float-tool-tip.tsx | 316 ------------------ .../toolbar/heading-controls.tsx | 57 ---- .../rich-text-editor/toolbar/index.tsx | 35 -- .../rich-text-editor/toolbar/link.tsx | 215 ------------ .../toolbar/table-controls.tsx | 55 --- .../tiptap/bubble-menu/link-selector.tsx | 3 +- .../tiptap/hooks/useDebouncedUpdates.tsx | 18 - .../tiptap/hooks/useNodeDeletion.tsx | 51 --- apps/app/components/tiptap/index.tsx | 2 +- .../tiptap/plugins/upload-image.tsx | 6 +- apps/app/components/tiptap/props.tsx | 4 +- apps/app/services/file.service.ts | 12 +- 15 files changed, 20 insertions(+), 1199 deletions(-) delete mode 100644 apps/app/components/rich-text-editor/index.tsx delete mode 100644 apps/app/components/rich-text-editor/mention-autocomplete.tsx delete mode 100644 apps/app/components/rich-text-editor/sample.tsx delete mode 100644 apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx delete mode 100644 apps/app/components/rich-text-editor/toolbar/heading-controls.tsx delete mode 100644 apps/app/components/rich-text-editor/toolbar/index.tsx delete mode 100644 apps/app/components/rich-text-editor/toolbar/link.tsx delete mode 100644 apps/app/components/rich-text-editor/toolbar/table-controls.tsx delete mode 100644 apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx delete mode 100644 apps/app/components/tiptap/hooks/useNodeDeletion.tsx diff --git a/apps/app/components/rich-text-editor/index.tsx b/apps/app/components/rich-text-editor/index.tsx deleted file mode 100644 index 1a657b6e4..000000000 --- a/apps/app/components/rich-text-editor/index.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { useCallback, useState, useImperativeHandle } from "react"; -import { useRouter } from "next/router"; - -import { InvalidContentHandler } from "remirror"; -import { - BoldExtension, - ItalicExtension, - CalloutExtension, - PlaceholderExtension, - CodeBlockExtension, - CodeExtension, - HistoryExtension, - LinkExtension, - UnderlineExtension, - HeadingExtension, - OrderedListExtension, - ListItemExtension, - BulletListExtension, - ImageExtension, - DropCursorExtension, - StrikeExtension, - MentionAtomExtension, - FontSizeExtension, -} from "remirror/extensions"; -import { - Remirror, - useRemirror, - EditorComponent, - OnChangeJSON, - OnChangeHTML, - FloatingToolbar, - FloatingWrapper, -} from "@remirror/react"; -import { TableExtension } from "@remirror/extension-react-tables"; -// tlds -import tlds from "tlds"; -// services -import fileService from "services/file.service"; -// components -import { CustomFloatingToolbar } from "./toolbar/float-tool-tip"; -import { MentionAutoComplete } from "./mention-autocomplete"; - -export interface IRemirrorRichTextEditor { - placeholder?: string; - mentions?: any[]; - tags?: any[]; - onBlur?: (jsonValue: any, htmlValue: any) => void; - onJSONChange?: (jsonValue: any) => void; - onHTMLChange?: (htmlValue: any) => void; - value?: any; - showToolbar?: boolean; - editable?: boolean; - customClassName?: string; - gptOption?: boolean; - noBorder?: boolean; - borderOnFocus?: boolean; - forwardedRef?: any; -} - -const RemirrorRichTextEditor: React.FC = (props) => { - const { - placeholder, - mentions = [], - tags = [], - onBlur = () => {}, - onJSONChange = () => {}, - onHTMLChange = () => {}, - value = "", - showToolbar = true, - editable = true, - customClassName, - gptOption = false, - noBorder = false, - borderOnFocus = true, - forwardedRef, - } = props; - - const [disableToolbar, setDisableToolbar] = useState(false); - - const router = useRouter(); - const { workspaceSlug } = router.query; - - // remirror error handler - const onError: InvalidContentHandler = useCallback( - ({ json, invalidContent, transformers }: any) => - // Automatically remove all invalid nodes and marks. - transformers.remove(json, invalidContent), - [] - ); - - const uploadImageHandler = (value: any): any => { - try { - const formData = new FormData(); - formData.append("asset", value[0].file); - formData.append("attributes", JSON.stringify({})); - - return [ - () => - new Promise(async (resolve, reject) => { - const imageUrl = await fileService - .uploadFile(workspaceSlug as string, formData) - .then((response) => response.asset); - - resolve({ - align: "left", - alt: "Not Found", - height: "100%", - width: "35%", - src: imageUrl, - }); - }), - ]; - } catch { - return []; - } - }; - - // remirror manager - const { manager, state } = useRemirror({ - extensions: () => [ - new BoldExtension(), - new ItalicExtension(), - new UnderlineExtension(), - new HeadingExtension({ levels: [1, 2, 3] }), - new FontSizeExtension({ defaultSize: "16", unit: "px" }), - new OrderedListExtension(), - new ListItemExtension(), - new BulletListExtension({ enableSpine: true }), - new CalloutExtension({ defaultType: "warn" }), - new CodeBlockExtension(), - new CodeExtension(), - new PlaceholderExtension({ - placeholder: placeholder || "Enter text...", - emptyNodeClass: "empty-node", - }), - new HistoryExtension(), - new LinkExtension({ - autoLink: true, - autoLinkAllowedTLDs: tlds, - selectTextOnClick: true, - defaultTarget: "_blank", - }), - new ImageExtension({ - enableResizing: true, - uploadHandler: uploadImageHandler, - createPlaceholder() { - const div = document.createElement("div"); - div.className = - "w-[35%] aspect-video bg-custom-background-80 text-custom-text-200 animate-pulse"; - return div; - }, - }), - new DropCursorExtension(), - new StrikeExtension(), - new MentionAtomExtension({ - matchers: [ - { name: "at", char: "@" }, - { name: "tag", char: "#" }, - ], - }), - new TableExtension(), - ], - content: value, - selection: "start", - stringHandler: "html", - onError, - }); - - useImperativeHandle(forwardedRef, () => ({ - clearEditor: () => { - manager.view.updateState(manager.createState({ content: "", selection: "start" })); - }, - setEditorValue: (value: any) => { - manager.view.updateState( - manager.createState({ - content: value, - selection: "end", - }) - ); - }, - })); - - return ( -
- { - const html = event.helpers.getHTML(); - const json = event.helpers.getJSON(); - - setDisableToolbar(true); - - onBlur(json, html); - }} - onFocus={() => setDisableToolbar(false)} - > -
- -
- - {editable && !disableToolbar && ( - - - - - - )} - - - {} - {} -
-
- ); -}; - -RemirrorRichTextEditor.displayName = "RemirrorRichTextEditor"; - -export default RemirrorRichTextEditor; diff --git a/apps/app/components/rich-text-editor/mention-autocomplete.tsx b/apps/app/components/rich-text-editor/mention-autocomplete.tsx deleted file mode 100644 index b0ba6955e..000000000 --- a/apps/app/components/rich-text-editor/mention-autocomplete.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useState, useEffect, FC } from "react"; -// remirror imports -import { cx } from "@remirror/core"; -import { useMentionAtom, MentionAtomNodeAttributes, FloatingWrapper } from "@remirror/react"; - -// export const; - -export interface IMentionAutoComplete { - mentions?: any[]; - tags?: any[]; -} - -export const MentionAutoComplete: FC = (props) => { - const { mentions = [], tags = [] } = props; - // states - const [options, setOptions] = useState([]); - - const { state, getMenuProps, getItemProps, indexIsHovered, indexIsSelected } = useMentionAtom({ - items: options, - }); - - useEffect(() => { - if (!state) { - return; - } - const searchTerm = state.query.full.toLowerCase(); - let filteredOptions: MentionAtomNodeAttributes[] = []; - - if (state.name === "tag") { - filteredOptions = tags.filter((tag) => tag?.label.toLowerCase().includes(searchTerm)); - } else if (state.name === "at") { - filteredOptions = mentions.filter((user) => user?.label.toLowerCase().includes(searchTerm)); - } - - filteredOptions = filteredOptions.sort().slice(0, 5); - setOptions(filteredOptions); - }, [state, mentions, tags]); - - const enabled = Boolean(state); - return ( - -
- {enabled && - options.map((user, index) => { - const isHighlighted = indexIsSelected(index); - const isHovered = indexIsHovered(index); - - return ( -
- {user.label} -
- ); - })} -
-
- ); -}; diff --git a/apps/app/components/rich-text-editor/sample.tsx b/apps/app/components/rich-text-editor/sample.tsx deleted file mode 100644 index f4b7c84fe..000000000 --- a/apps/app/components/rich-text-editor/sample.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { TableExtension } from "@remirror/extension-react-tables"; -import { - EditorComponent, - ReactComponentExtension, - Remirror, - TableComponents, - tableControllerPluginKey, - ThemeProvider, - useCommands, - useRemirror, - useRemirrorContext, -} from "@remirror/react"; -import type { AnyExtension } from "remirror"; - -const CommandMenu: React.FC = () => { - const { createTable, ...commands } = useCommands(); - - return ( -
-

commands:

-

- - - - - - - - -

-
- ); -}; - -const ProsemirrorDocData: React.FC = () => { - const ctx = useRemirrorContext({ autoUpdate: false }); - const [jsonPluginState, setJsonPluginState] = useState(""); - const [jsonDoc, setJsonDoc] = useState(""); - const { addHandler, view } = ctx; - - useEffect(() => { - addHandler("updated", () => { - setJsonDoc(JSON.stringify(view.state.doc.toJSON(), null, 2)); - - const pluginStateValues = tableControllerPluginKey.getState(view.state)?.values; - setJsonPluginState( - JSON.stringify({ ...pluginStateValues, tableNodeResult: "hidden" }, null, 2) - ); - }); - }, [addHandler, view]); - - return ( -
-

tableControllerPluginKey.getState(view.state)

-
-        {jsonPluginState}
-      
-

view.state.doc.toJSON()

-
-        {jsonDoc}
-      
-
- ); -}; - -const Table = ({ - children, - extensions, -}: { - children?: React.ReactElement; - extensions: () => AnyExtension[]; -}): JSX.Element => { - const { manager, state } = useRemirror({ extensions }); - - return ( - - - - - - - {children} - - - ); -}; - -const Basic = (): JSX.Element => ; - -const defaultExtensions = () => [new ReactComponentExtension(), new TableExtension()]; - -export default Basic; diff --git a/apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx b/apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx deleted file mode 100644 index 9fd825807..000000000 --- a/apps/app/components/rich-text-editor/toolbar/float-tool-tip.tsx +++ /dev/null @@ -1,316 +0,0 @@ -import React, { - ChangeEvent, - HTMLProps, - KeyboardEvent, - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from "react"; - -import { createMarkPositioner, LinkExtension, ShortcutHandlerProps } from "remirror/extensions"; -// buttons -import { - ToggleBoldButton, - ToggleItalicButton, - ToggleUnderlineButton, - ToggleStrikeButton, - ToggleOrderedListButton, - ToggleBulletListButton, - ToggleCodeButton, - ToggleHeadingButton, - useActive, - CommandButton, - useAttrs, - useChainedCommands, - useCurrentSelection, - useExtensionEvent, - useUpdateReason, -} from "@remirror/react"; -import { EditorState } from "remirror"; - -type Props = { - gptOption?: boolean; - editorState: Readonly; - setDisableToolbar: React.Dispatch>; -}; - -const useLinkShortcut = () => { - const [linkShortcut, setLinkShortcut] = useState(); - const [isEditing, setIsEditing] = useState(false); - - useExtensionEvent( - LinkExtension, - "onShortcut", - useCallback( - (props) => { - if (!isEditing) { - setIsEditing(true); - } - - return setLinkShortcut(props); - }, - [isEditing] - ) - ); - - return { linkShortcut, isEditing, setIsEditing }; -}; - -const useFloatingLinkState = () => { - const chain = useChainedCommands(); - const { isEditing, linkShortcut, setIsEditing } = useLinkShortcut(); - const { to, empty } = useCurrentSelection(); - - const url = (useAttrs().link()?.href as string) ?? ""; - const [href, setHref] = useState(url); - - // A positioner which only shows for links. - const linkPositioner = useMemo(() => createMarkPositioner({ type: "link" }), []); - - const onRemove = useCallback(() => chain.removeLink().focus().run(), [chain]); - - const updateReason = useUpdateReason(); - - useLayoutEffect(() => { - if (!isEditing) { - return; - } - - if (updateReason.doc || updateReason.selection) { - setIsEditing(false); - } - }, [isEditing, setIsEditing, updateReason.doc, updateReason.selection]); - - useEffect(() => { - setHref(url); - }, [url]); - - const submitHref = useCallback(() => { - setIsEditing(false); - const range = linkShortcut ?? undefined; - - if (href === "") { - chain.removeLink(); - } else { - chain.updateLink({ href, auto: false }, range); - } - - chain.focus(range?.to ?? to).run(); - }, [setIsEditing, linkShortcut, chain, href, to]); - - const cancelHref = useCallback(() => { - setIsEditing(false); - }, [setIsEditing]); - - const clickEdit = useCallback(() => { - if (empty) { - chain.selectLink(); - } - - setIsEditing(true); - }, [chain, empty, setIsEditing]); - - return useMemo( - () => ({ - href, - setHref, - linkShortcut, - linkPositioner, - isEditing, - setIsEditing, - clickEdit, - onRemove, - submitHref, - cancelHref, - }), - [ - href, - linkShortcut, - linkPositioner, - isEditing, - clickEdit, - onRemove, - submitHref, - cancelHref, - setIsEditing, - ] - ); -}; - -const DelayAutoFocusInput = ({ - autoFocus, - setDisableToolbar, - ...rest -}: HTMLProps & { - setDisableToolbar: React.Dispatch>; -}) => { - const inputRef = useRef(null); - - useEffect(() => { - if (!autoFocus) { - return; - } - - setDisableToolbar(false); - - const frame = window.requestAnimationFrame(() => { - inputRef.current?.focus(); - }); - - return () => { - window.cancelAnimationFrame(frame); - }; - }, [autoFocus, setDisableToolbar]); - - useEffect(() => { - setDisableToolbar(false); - }, [setDisableToolbar]); - - return ( - <> - - { - if (rest.onKeyDown) rest.onKeyDown(e); - setDisableToolbar(false); - }} - className={`${rest.className} mt-1`} - onFocus={() => { - setDisableToolbar(false); - }} - onBlur={() => { - setDisableToolbar(true); - }} - /> - - ); -}; - -export const CustomFloatingToolbar: React.FC = ({ - gptOption, - editorState, - setDisableToolbar, -}) => { - const { isEditing, setIsEditing, clickEdit, onRemove, submitHref, href, setHref, cancelHref } = - useFloatingLinkState(); - - const active = useActive(); - const activeLink = active.link(); - - const handleClickEdit = useCallback(() => { - clickEdit(); - }, [clickEdit]); - - return ( -
-
-
- - - -
-
- - - - -
-
- - -
- {gptOption && ( -
- -
- )} -
- -
- {activeLink ? ( -
- { - window.open(href, "_blank"); - }} - icon="externalLinkFill" - enabled - /> - - -
- ) : ( - { - if (isEditing) { - setIsEditing(false); - } else { - handleClickEdit(); - } - }} - icon="link" - enabled - active={isEditing} - /> - )} -
- - {isEditing && ( -
- ) => setHref(e.target.value)} - value={href} - onKeyDown={(e: KeyboardEvent) => { - const { code } = e; - - if (code === "Enter") { - submitHref(); - } - - if (code === "Escape") { - cancelHref(); - } - }} - /> -
- )} -
- ); -}; diff --git a/apps/app/components/rich-text-editor/toolbar/heading-controls.tsx b/apps/app/components/rich-text-editor/toolbar/heading-controls.tsx deleted file mode 100644 index 3297958f0..000000000 --- a/apps/app/components/rich-text-editor/toolbar/heading-controls.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// remirror -import { useCommands, useActive } from "@remirror/react"; -// ui -import { CustomMenu } from "components/ui"; - -const HeadingControls = () => { - const { toggleHeading, focus } = useCommands(); - - const active = useActive(); - - return ( -
- - { - toggleHeading({ level: 1 }); - focus(); - }} - className={`${active.heading({ level: 1 }) ? "bg-indigo-50" : ""}`} - > - Heading 1 - - { - toggleHeading({ level: 2 }); - focus(); - }} - className={`${active.heading({ level: 2 }) ? "bg-indigo-50" : ""}`} - > - Heading 2 - - { - toggleHeading({ level: 3 }); - focus(); - }} - className={`${active.heading({ level: 3 }) ? "bg-indigo-50" : ""}`} - > - Heading 3 - - -
- ); -}; - -export default HeadingControls; diff --git a/apps/app/components/rich-text-editor/toolbar/index.tsx b/apps/app/components/rich-text-editor/toolbar/index.tsx deleted file mode 100644 index 8362ced57..000000000 --- a/apps/app/components/rich-text-editor/toolbar/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// buttons -import { - ToggleBoldButton, - ToggleItalicButton, - ToggleUnderlineButton, - ToggleStrikeButton, - ToggleOrderedListButton, - ToggleBulletListButton, - RedoButton, - UndoButton, -} from "@remirror/react"; -// headings -import HeadingControls from "./heading-controls"; - -export const RichTextToolbar: React.FC = () => ( -
-
- - -
-
- -
-
- - - - -
-
- - -
-
-); diff --git a/apps/app/components/rich-text-editor/toolbar/link.tsx b/apps/app/components/rich-text-editor/toolbar/link.tsx deleted file mode 100644 index 045736b99..000000000 --- a/apps/app/components/rich-text-editor/toolbar/link.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import React, { - ChangeEvent, - HTMLProps, - KeyboardEvent, - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from "react"; - -import { createMarkPositioner, LinkExtension, ShortcutHandlerProps } from "remirror/extensions"; -import { - CommandButton, - FloatingToolbar, - FloatingWrapper, - useActive, - useAttrs, - useChainedCommands, - useCurrentSelection, - useExtensionEvent, - useUpdateReason, -} from "@remirror/react"; - -const useLinkShortcut = () => { - const [linkShortcut, setLinkShortcut] = useState(); - const [isEditing, setIsEditing] = useState(false); - - useExtensionEvent( - LinkExtension, - "onShortcut", - useCallback( - (props) => { - if (!isEditing) { - setIsEditing(true); - } - - return setLinkShortcut(props); - }, - [isEditing] - ) - ); - - return { linkShortcut, isEditing, setIsEditing }; -}; - -const useFloatingLinkState = () => { - const chain = useChainedCommands(); - const { isEditing, linkShortcut, setIsEditing } = useLinkShortcut(); - const { to, empty } = useCurrentSelection(); - - const url = (useAttrs().link()?.href as string) ?? ""; - const [href, setHref] = useState(url); - - // A positioner which only shows for links. - const linkPositioner = useMemo(() => createMarkPositioner({ type: "link" }), []); - - const onRemove = useCallback(() => chain.removeLink().focus().run(), [chain]); - - const updateReason = useUpdateReason(); - - useLayoutEffect(() => { - if (!isEditing) { - return; - } - - if (updateReason.doc || updateReason.selection) { - setIsEditing(false); - } - }, [isEditing, setIsEditing, updateReason.doc, updateReason.selection]); - - useEffect(() => { - setHref(url); - }, [url]); - - const submitHref = useCallback(() => { - setIsEditing(false); - const range = linkShortcut ?? undefined; - - if (href === "") { - chain.removeLink(); - } else { - chain.updateLink({ href, auto: false }, range); - } - - chain.focus(range?.to ?? to).run(); - }, [setIsEditing, linkShortcut, chain, href, to]); - - const cancelHref = useCallback(() => { - setIsEditing(false); - }, [setIsEditing]); - - const clickEdit = useCallback(() => { - if (empty) { - chain.selectLink(); - } - - setIsEditing(true); - }, [chain, empty, setIsEditing]); - - return useMemo( - () => ({ - href, - setHref, - linkShortcut, - linkPositioner, - isEditing, - clickEdit, - onRemove, - submitHref, - cancelHref, - }), - [href, linkShortcut, linkPositioner, isEditing, clickEdit, onRemove, submitHref, cancelHref] - ); -}; - -const DelayAutoFocusInput = ({ autoFocus, ...rest }: HTMLProps) => { - const inputRef = useRef(null); - - useEffect(() => { - if (!autoFocus) { - return; - } - - const frame = window.requestAnimationFrame(() => { - inputRef.current?.focus(); - }); - - return () => { - window.cancelAnimationFrame(frame); - }; - }, [autoFocus]); - - return ; -}; - -export const FloatingLinkToolbar = () => { - const { isEditing, linkPositioner, clickEdit, onRemove, submitHref, href, setHref, cancelHref } = - useFloatingLinkState(); - - const active = useActive(); - const activeLink = active.link(); - - const { empty } = useCurrentSelection(); - - const handleClickEdit = useCallback(() => { - clickEdit(); - }, [clickEdit]); - - const linkEditButtons = activeLink ? ( - <> - { - window.open(href, "_blank"); - }} - icon="externalLinkFill" - enabled - /> - - - - ) : ( - - ); - - return ( - <> - {!isEditing && ( - - {linkEditButtons} - - )} - {!isEditing && empty && ( - - {linkEditButtons} - - )} - - - ) => setHref(e.target.value)} - value={href} - onKeyDown={(e: KeyboardEvent) => { - const { code } = e; - - if (code === "Enter") { - submitHref(); - } - - if (code === "Escape") { - cancelHref(); - } - }} - /> - - - ); -}; diff --git a/apps/app/components/rich-text-editor/toolbar/table-controls.tsx b/apps/app/components/rich-text-editor/toolbar/table-controls.tsx deleted file mode 100644 index 20f49b6db..000000000 --- a/apps/app/components/rich-text-editor/toolbar/table-controls.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useCommands } from "@remirror/react"; - -export const TableControls = () => { - const { createTable, ...commands } = useCommands(); - - return ( -
- - -
- ); -}; diff --git a/apps/app/components/tiptap/bubble-menu/link-selector.tsx b/apps/app/components/tiptap/bubble-menu/link-selector.tsx index 62331ebee..c58c0de0b 100644 --- a/apps/app/components/tiptap/bubble-menu/link-selector.tsx +++ b/apps/app/components/tiptap/bubble-menu/link-selector.tsx @@ -41,7 +41,8 @@ export const LinkSelector: FC = ({
{ e.preventDefault(); - const input = e.target[0] as HTMLInputElement; + const form = e.target as HTMLFormElement; + const input = form.elements[0] as HTMLInputElement; editor.chain().focus().setLink({ href: input.value }).run(); setIsOpen(false); }} diff --git a/apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx b/apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx deleted file mode 100644 index 0fc835a98..000000000 --- a/apps/app/components/tiptap/hooks/useDebouncedUpdates.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useDebouncedCallback } from 'use-debounce'; -import { Editor as CoreEditor } from "@tiptap/core"; - -type DebouncedUpdatesProps = { - onChange?: (json: any, html: string) => void; - editor: CoreEditor | null; -}; - -export const useDebouncedUpdates = (props: DebouncedUpdatesProps) => - useDebouncedCallback(async () => { - setTimeout(async () => { - if (props.onChange) { - props.onChange(props.editor.getJSON(), props.editor.getHTML()); - } - }, 500); - }, 1000); -; - diff --git a/apps/app/components/tiptap/hooks/useNodeDeletion.tsx b/apps/app/components/tiptap/hooks/useNodeDeletion.tsx deleted file mode 100644 index 9370947b6..000000000 --- a/apps/app/components/tiptap/hooks/useNodeDeletion.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useCallback, useRef } from 'react'; -import { Node } from "@tiptap/pm/model"; -import { Editor as CoreEditor } from "@tiptap/core"; -import { EditorState } from '@tiptap/pm/state'; -import fileService from 'services/file.service'; - -export const useNodeDeletion = () => { - const previousState = useRef(); - - const onNodeDeleted = useCallback( - async (node: Node) => { - if (node.type.name === 'image') { - const assetUrlWithWorkspaceId = new URL(node.attrs.src).pathname.substring(1); - const resStatus = await fileService.deleteFile(assetUrlWithWorkspaceId); - if (resStatus === 204) { - console.log("file deleted successfully"); - } - } - }, - [], - ); - - const checkForNodeDeletions = useCallback( - (editor: CoreEditor) => { - const prevNodesById: Record = {}; - previousState.current?.doc.forEach((node) => { - if (node.attrs.id) { - prevNodesById[node.attrs.id] = node; - } - }); - - const nodesById: Record = {}; - editor.state?.doc.forEach((node) => { - if (node.attrs.id) { - nodesById[node.attrs.id] = node; - } - }); - - previousState.current = editor.state; - - for (const [id, node] of Object.entries(prevNodesById)) { - if (nodesById[id] === undefined) { - onNodeDeleted(node); - } - } - }, - [onNodeDeleted], - ); - - return { checkForNodeDeletions }; -}; diff --git a/apps/app/components/tiptap/index.tsx b/apps/app/components/tiptap/index.tsx index da5e0db7a..26f6d86af 100644 --- a/apps/app/components/tiptap/index.tsx +++ b/apps/app/components/tiptap/index.tsx @@ -73,7 +73,7 @@ const Tiptap = (props: ITiptapRichTextEditor) => { async (node: Node) => { if (node.type.name === 'image') { const assetUrlWithWorkspaceId = new URL(node.attrs.src).pathname.substring(1); - const resStatus = await fileService.deleteFile(assetUrlWithWorkspaceId); + const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId); if (resStatus === 204) { console.log("file deleted successfully"); } diff --git a/apps/app/components/tiptap/plugins/upload-image.tsx b/apps/app/components/tiptap/plugins/upload-image.tsx index d92aa38aa..692dd2ca4 100644 --- a/apps/app/components/tiptap/plugins/upload-image.tsx +++ b/apps/app/components/tiptap/plugins/upload-image.tsx @@ -14,7 +14,7 @@ const UploadImagesPlugin = () => apply(tr, set) { set = set.map(tr.mapping, tr.doc); // See if the transaction adds or removes any placeholders - const action = tr.getMeta(this); + const action = tr.getMeta(uploadKey); if (action && action.add) { const { id, pos, src } = action.add; @@ -33,7 +33,7 @@ const UploadImagesPlugin = () => set = set.add(tr.doc, [deco]); } else if (action && action.remove) { set = set.remove( - set.find(null, null, (spec) => spec.id == action.remove.id), + set.find(undefined, undefined, (spec) => spec.id == action.remove.id), ); } return set; @@ -50,7 +50,7 @@ export default UploadImagesPlugin; function findPlaceholder(state: EditorState, id: {}) { const decos = uploadKey.getState(state); - const found = decos.find(null, null, (spec) => spec.id == id); + const found = decos.find(undefined, undefined, (spec: { id: number | undefined }) => spec.id == id); return found.length ? found[0].from : null; } diff --git a/apps/app/components/tiptap/props.tsx b/apps/app/components/tiptap/props.tsx index ea7f71b59..1ffbebe6d 100644 --- a/apps/app/components/tiptap/props.tsx +++ b/apps/app/components/tiptap/props.tsx @@ -45,7 +45,9 @@ export const TiptapEditorProps: EditorProps = { top: event.clientY, }); // here we deduct 1 from the pos or else the image will create an extra node - startImageUpload(file, view, coordinates.pos - 1); + if (coordinates) { + startImageUpload(file, view, coordinates.pos - 1); + } return true; } return false; diff --git a/apps/app/services/file.service.ts b/apps/app/services/file.service.ts index a80b2ce80..d2f01428d 100644 --- a/apps/app/services/file.service.ts +++ b/apps/app/services/file.service.ts @@ -40,7 +40,7 @@ class FileServices extends APIService { }); } - async deleteFile(assetUrlWithWorkspaceId: string): Promise { + async deleteImage(assetUrlWithWorkspaceId: string): Promise { return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`) .then((response) => response?.status) .catch((error) => { @@ -48,6 +48,16 @@ class FileServices extends APIService { }); } + async deleteFile(workspaceId: string, assetUrl: string): Promise { + const lastIndex = assetUrl.lastIndexOf("/"); + const assetId = assetUrl.substring(lastIndex + 1); + + return this.delete(`/api/workspaces/file-assets/${workspaceId}/${assetId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } async uploadUserFile(file: FormData): Promise { return this.mediaUpload(`/api/users/file-assets/`, file) .then((response) => response?.data) From ee6a6ecf3f9bea83476cbe49aea4628ccf3e5584 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 03:31:38 +0530 Subject: [PATCH 20/27] fixing node ts error for Horizontal Rule --- apps/app/components/tiptap/extensions/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/app/components/tiptap/extensions/index.tsx b/apps/app/components/tiptap/extensions/index.tsx index 14d7106eb..5af141586 100644 --- a/apps/app/components/tiptap/extensions/index.tsx +++ b/apps/app/components/tiptap/extensions/index.tsx @@ -14,6 +14,7 @@ import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { lowlight } from 'lowlight/lib/core' import SlashCommand from "../slash-command"; import { InputRule } from "@tiptap/core"; +import { Node as ProseMirrorNode } from '@tiptap/pm/model'; import ts from 'highlight.js/lib/languages/typescript' @@ -80,8 +81,8 @@ export const TiptapExtensions = [ const { tr } = state; const start = range.from; const end = range.to; - - tr.insert(start - 1, this.type.create(attributes)).delete( + const node = this.type.create(attributes) as unknown as ProseMirrorNode; + tr.insert(start - 1, node).delete( tr.mapping.map(start), tr.mapping.map(end), ); From 2329abe7dd5fd7398d1f214defe8272d2ab4caca Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 03:40:01 +0530 Subject: [PATCH 21/27] added ts fix for node types --- apps/app/components/tiptap/extensions/index.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/app/components/tiptap/extensions/index.tsx b/apps/app/components/tiptap/extensions/index.tsx index 5af141586..6eed0108a 100644 --- a/apps/app/components/tiptap/extensions/index.tsx +++ b/apps/app/components/tiptap/extensions/index.tsx @@ -75,18 +75,16 @@ export const TiptapExtensions = [ return [ new InputRule({ find: /^(?:---|—-|___\s|\*\*\*\s)$/, - handler: ({ state, range }) => { - const attributes = {}; + handler: ({ state, range, commands }) => { + commands.splitBlock(); + const attributes = {}; const { tr } = state; const start = range.from; const end = range.to; - const node = this.type.create(attributes) as unknown as ProseMirrorNode; - tr.insert(start - 1, node).delete( - tr.mapping.map(start), - tr.mapping.map(end), - ); - }, + // @ts-ignore + tr.replaceWith(start - 1, end, this.type.create(attributes)); + } }), ]; }, From 1c08ad506a22592a82fffef2a03c8a68b888df4b Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 03:50:59 +0530 Subject: [PATCH 22/27] temp fix --- apps/app/components/tiptap/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/app/components/tiptap/index.tsx b/apps/app/components/tiptap/index.tsx index 26f6d86af..14e77825b 100644 --- a/apps/app/components/tiptap/index.tsx +++ b/apps/app/components/tiptap/index.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import { useEditor, EditorContent, Editor } from '@tiptap/react'; import { useDebouncedCallback } from 'use-debounce'; import { EditorBubbleMenu } from './bubble-menu'; From 6c9b31a2c4fe51500541196b83ea77b8a25f2ba9 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 03:57:16 +0530 Subject: [PATCH 23/27] temp fix --- apps/app/components/tiptap/plugins/upload-image.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/app/components/tiptap/plugins/upload-image.tsx b/apps/app/components/tiptap/plugins/upload-image.tsx index 692dd2ca4..3f372d9d4 100644 --- a/apps/app/components/tiptap/plugins/upload-image.tsx +++ b/apps/app/components/tiptap/plugins/upload-image.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state"; import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view"; import fileService from "services/file.service"; From b86081d788cd46e9d5d2889444f2ff784d6b8cad Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 04:11:44 +0530 Subject: [PATCH 24/27] added min height for issue description in modal --- apps/app/components/issues/form.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index c2a32b4e4..035d6451e 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -35,8 +35,8 @@ import { import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types import type { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types"; -// rich-text-editor import Tiptap from "components/tiptap"; +// rich-text-editor const defaultValues: Partial = { project: "", @@ -360,6 +360,7 @@ export const IssueForm: FC = ({ ? watch("description_html") : value } + customClassName="min-h-[150px]" onChange={(description: Object, description_html: string) => { onChange(description_html); setValue("description", description); From c1d2b41a80140ab7e62044027fc42cf3e593f634 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:23:33 +0530 Subject: [PATCH 25/27] added resolutions to prosemirror-model version --- apps/app/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/app/package.json b/apps/app/package.json index d9911841c..ea1f41f1a 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -95,5 +95,8 @@ "tailwindcss": "^3.1.6", "tsconfig": "*", "typescript": "4.7.4" + }, + "resolutions": { + "prosemirror-model": "1.9.1" } } From 7b3862a3dd7a653c921fee301bc9b2343e2f9c0b Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:17:53 +0530 Subject: [PATCH 26/27] trying pnpm overrides --- apps/app/package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/app/package.json b/apps/app/package.json index ea1f41f1a..403cb6fd8 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -98,5 +98,10 @@ }, "resolutions": { "prosemirror-model": "1.9.1" + }, + "pnpm": { + "overrides": { + "prosemirror-model": "1.9.1" + } } } From 124383ebb430cb17303cbebdb1b20e0bcf369250 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:16:25 +0530 Subject: [PATCH 27/27] explicitly added prosemirror deps --- apps/app/package.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/app/package.json b/apps/app/package.json index 403cb6fd8..6b9a9d9a7 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -58,6 +58,13 @@ "next-pwa": "^5.6.0", "next-themes": "^0.2.1", "nprogress": "^0.2.0", + "prosemirror-commands": "^1.5.2", + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.19.3", + "prosemirror-schema-list": "^1.3.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.7.4", + "prosemirror-view": "^1.31.7", "react": "18.2.0", "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", @@ -97,11 +104,6 @@ "typescript": "4.7.4" }, "resolutions": { - "prosemirror-model": "1.9.1" - }, - "pnpm": { - "overrides": { - "prosemirror-model": "1.9.1" - } + "prosemirror-model": "1.18.1" } }