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";
// 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<IssueFormProps> = ({
reValidateMode: "onChange",
});
console.log("values", getValues());
const issueName = watch("name");
const handleCreateUpdateIssue = async (formData: Partial<IIssue>) => {
@ -338,9 +341,8 @@ export const IssueForm: FC<IssueFormProps> = ({
{issueName && issueName !== "" && (
<button
type="button"
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${
iAmFeelingLucky ? "cursor-wait" : ""
}`}
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${iAmFeelingLucky ? "cursor-wait" : ""
}`}
onClick={handleAutoGenerateDescription}
disabled={iAmFeelingLucky}
>
@ -362,23 +364,27 @@ export const IssueForm: FC<IssueFormProps> = ({
AI
</button>
</div>
{/* <Controller */}
{/* name="description" */}
{/* control={control} */}
{/* render={({ field: { value } }) => ( */}
{/* <WrappedRemirrorRichTextEditor */}
{/* value={ */}
{/* !value || (typeof value === "object" && Object.keys(value).length === 0) */}
{/* ? watch("description_html") */}
{/* : value */}
{/* } */}
{/* onJSONChange={(jsonValue) => setValue("description", jsonValue)} */}
{/* onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} */}
{/* placeholder="Description" */}
{/* ref={editorRef} */}
{/* /> */}
{/* )} */}
{/* /> */}
<Controller
name="description_html"
control={control}
render={({ field: { value, onChange } }) => {
if (!value && !watch("description_html")) return <></>;
return (
<Tiptap
value={
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
? watch("description_html")
: value
}
onChange={(description: Object, description_html: string) => {
onChange(description_html);
setValue("description", description);
}}
/>
);
}}
/>
<GptAssistantModal
isOpen={gptAssistantModal}
handleClose={() => {
@ -523,7 +529,7 @@ export const IssueForm: FC<IssueFormProps> = ({
onClick={() => setCreateMore((prevData) => !prevData)}
>
<span className="text-xs">Create more</span>
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
<ToggleSwitch value={createMore} onChange={() => { }} size="md" />
</div>
<div className="flex items-center gap-2">
<SecondaryButton onClick={handleClose}>Discard</SecondaryButton>
@ -533,8 +539,8 @@ export const IssueForm: FC<IssueFormProps> = ({
? "Updating Issue..."
: "Update Issue"
: isSubmitting
? "Adding Issue..."
: "Add Issue"}
? "Adding Issue..."
: "Add Issue"}
</PrimaryButton>
</div>
</div>

View File

@ -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,

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;
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<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(
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 && <EditorBubbleMenu editor={editor} />}
<div className="pt-8">
<div className="pt-9">
<EditorContent editor={editor} />
</div>
</div>