improved file structure and delete image function implemented

This commit is contained in:
Palanikannan1437 2023-08-09 01:57:08 +05:30
parent c078d59916
commit 5228ab8d0a
15 changed files with 152 additions and 99 deletions

4
.gitignore vendored
View File

@ -70,4 +70,6 @@ package-lock.json
# lock files
package-lock.json
pnpm-lock.yaml
pnpm-workspace.yaml
pnpm-workspace.yaml
.npmrc

View File

@ -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<IssueDetailsProps> = ({
setIsSubmitting={setIsSubmitting}
onChange={(description: Object, description_html: string) => {
onChange(description_html);
// setValue("description_html", description_html);
setValue("description", description);
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting(false));
}}

View File

@ -1,28 +0,0 @@
import fileService from 'services/file.service';
const UploadImageHandler = (file: File): Promise<string> => {
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;

View File

@ -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 (
<div
onClick={() => {
editor?.chain().focus().run();
}}
className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`}
>
{editor && <EditorBubbleMenu editor={editor} />}
<div className="pt-8">
<EditorContent editor={editor} />
</div>
</div>
);
};
export default Tiptap;

View File

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

View File

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

View File

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

View File

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

View File

@ -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<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') {
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],
);
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 (
<div
onClick={() => {
editor?.chain().focus().run();
}}
className={`tiptap-editor-container relative min-h-[150px] ${editorClassNames}`}
>
{editor && <EditorBubbleMenu editor={editor} />}
<div className="pt-8">
<EditorContent editor={editor} />
</div>
</div>
);
};
export default Tiptap;

View File

@ -42,7 +42,7 @@ const UploadImagesPlugin = () =>
props: {
decorations(state) {
return this.getState(state);
},
}
},
});

View File

@ -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: <ImageIcon size={18} />,
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();

View File

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

View File

@ -40,12 +40,9 @@ class FileServices extends APIService {
});
}
async deleteFile(workspaceId: string, assetUrl: string): Promise<any> {
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<any> {
return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`)
.then((response) => response?.status)
.catch((error) => {
throw error?.response?.data;
});