Integrated tiptap with Issue Modal

This commit is contained in:
Palanikannan1437 2023-08-09 09:42:57 +05:30
parent 5228ab8d0a
commit 5c290e1302
5 changed files with 104 additions and 36 deletions

View File

@ -36,6 +36,7 @@ import {
import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline";
// types // types
import type { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types"; import type { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types";
import Tiptap from "components/tiptap";
// rich-text-editor // rich-text-editor
// const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { // const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), {
// ssr: false, // ssr: false,
@ -145,6 +146,8 @@ export const IssueForm: FC<IssueFormProps> = ({
reValidateMode: "onChange", reValidateMode: "onChange",
}); });
console.log("values", getValues());
const issueName = watch("name"); const issueName = watch("name");
const handleCreateUpdateIssue = async (formData: Partial<IIssue>) => { const handleCreateUpdateIssue = async (formData: Partial<IIssue>) => {
@ -338,8 +341,7 @@ export const IssueForm: FC<IssueFormProps> = ({
{issueName && issueName !== "" && ( {issueName && issueName !== "" && (
<button <button
type="button" type="button"
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${ className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${iAmFeelingLucky ? "cursor-wait" : ""
iAmFeelingLucky ? "cursor-wait" : ""
}`} }`}
onClick={handleAutoGenerateDescription} onClick={handleAutoGenerateDescription}
disabled={iAmFeelingLucky} disabled={iAmFeelingLucky}
@ -362,23 +364,27 @@ export const IssueForm: FC<IssueFormProps> = ({
AI AI
</button> </button>
</div> </div>
{/* <Controller */} <Controller
{/* name="description" */} name="description_html"
{/* control={control} */} control={control}
{/* render={({ field: { value } }) => ( */} render={({ field: { value, onChange } }) => {
{/* <WrappedRemirrorRichTextEditor */} if (!value && !watch("description_html")) return <></>;
{/* value={ */}
{/* !value || (typeof value === "object" && Object.keys(value).length === 0) */} return (
{/* ? watch("description_html") */} <Tiptap
{/* : value */} value={
{/* } */} !value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
{/* onJSONChange={(jsonValue) => setValue("description", jsonValue)} */} ? watch("description_html")
{/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */} : value
{/* placeholder="Description" */} }
{/* ref={editorRef} */} onChange={(description: Object, description_html: string) => {
{/* /> */} onChange(description_html);
{/* )} */} setValue("description", description);
{/* /> */} }}
/>
);
}}
/>
<GptAssistantModal <GptAssistantModal
isOpen={gptAssistantModal} isOpen={gptAssistantModal}
handleClose={() => { handleClose={() => {

View File

@ -54,7 +54,7 @@ export const TiptapExtensions = [
code: { code: {
HTMLAttributes: { HTMLAttributes: {
class: 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", spellcheck: "false",
}, },
}, },
@ -62,7 +62,7 @@ export const TiptapExtensions = [
horizontalRule: false, horizontalRule: false,
dropcursor: { dropcursor: {
color: "#DBEAFE", color: "#DBEAFE",
width: 4, width: 2,
}, },
gapcursor: false, gapcursor: false,
}), }),
@ -117,7 +117,7 @@ export const TiptapExtensions = [
includeChildren: true, includeChildren: true,
}), }),
UniqueID.configure({ UniqueID.configure({
types: ['heading', 'paragraph', 'image'], types: ['image'],
}), }),
SlashCommand, SlashCommand,
TiptapUnderline, TiptapUnderline,

View File

@ -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);
;

View File

@ -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<EditorState>();
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<string, Node> = {};
previousState.current?.doc.forEach((node) => {
if (node.attrs.id) {
prevNodesById[node.attrs.id] = node;
}
});
const nodesById: Record<string, Node> = {};
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 };
};

View File

@ -15,7 +15,7 @@ type TiptapProps = {
borderOnFocus?: boolean; borderOnFocus?: boolean;
customClassName?: string; customClassName?: string;
onChange?: (json: any, html: string) => void; onChange?: (json: any, html: string) => void;
setIsSubmitting: (isSubmitting: boolean) => void; setIsSubmitting?: (isSubmitting: boolean) => void;
} }
const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => { const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, customClassName }: TiptapProps) => {
@ -24,7 +24,7 @@ const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, cus
extensions: TiptapExtensions, extensions: TiptapExtensions,
content: value, content: value,
onUpdate: async ({ editor }) => { onUpdate: async ({ editor }) => {
setIsSubmitting(true); setIsSubmitting?.(true);
checkForNodeDeletions(editor) checkForNodeDeletions(editor)
debouncedUpdates({ onChange, editor }); debouncedUpdates({ onChange, editor });
} }
@ -32,13 +32,6 @@ const Tiptap = ({ onChange, setIsSubmitting, value, noBorder, borderOnFocus, cus
const previousState = useRef<EditorState>(); const previousState = useRef<EditorState>();
const extractPath = useCallback((url: string, searchString: string) => {
if (url.startsWith(searchString)) {
console.log("chala", url, searchString)
return url.substring(searchString.length);
}
}, []);
const onNodeDeleted = useCallback( const onNodeDeleted = useCallback(
async (node: Node) => { async (node: Node) => {
if (node.type.name === 'image') { 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}`} className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`}
> >
{editor && <EditorBubbleMenu editor={editor} />} {editor && <EditorBubbleMenu editor={editor} />}
<div className="pt-8"> <div className="pt-9">
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </div>
</div> </div>