From d8308cfce43946cec1c753dbaa27ae30df456800 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 12 Sep 2024 16:26:00 +0530 Subject: [PATCH] fix: image insertion at pos and loading effect added --- .../src/core/components/menus/menu-items.ts | 9 ++-- .../src/core/extensions/core-without-props.ts | 4 +- .../editor/src/core/extensions/extensions.tsx | 4 +- .../components/image-block-view.tsx | 5 ++ .../image-block/components/image-loader.tsx | 3 ++ .../extensions/image-block/image-block.ts | 53 +++++++------------ .../extensions/image-upload/image-upload.ts | 27 ++-------- .../image-upload/view/image-upload.tsx | 2 +- ....tsx => image-component-without-props.tsx} | 26 +++------ packages/editor/src/styles/drag-drop.css | 4 +- 10 files changed, 50 insertions(+), 87 deletions(-) create mode 100644 packages/editor/src/core/extensions/image-block/components/image-loader.tsx rename packages/editor/src/core/extensions/image/{image-block-without-props.tsx => image-component-without-props.tsx} (58%) diff --git a/packages/editor/src/core/components/menus/menu-items.ts b/packages/editor/src/core/components/menus/menu-items.ts index 71d1576d8d..63d76117ab 100644 --- a/packages/editor/src/core/components/menus/menu-items.ts +++ b/packages/editor/src/core/components/menus/menu-items.ts @@ -189,16 +189,17 @@ export const TableItem = (editor: Editor): EditorMenuItem => ({ icon: TableIcon, }); -export const ImageItem = (editor: Editor, uploadFile: UploadImage) => +export const ImageItem = (editor: Editor) => ({ key: "image", name: "Image", isActive: () => editor?.isActive("image"), - command: (savedSelection: Selection | null) => editor?.commands.setImageUpload({ event: "insert" }), + command: (savedSelection: Selection | null) => + editor?.commands.setImageUpload({ event: "insert", pos: savedSelection?.from }), icon: ImageIcon, }) as const; -export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImage) { +export function getEditorMenuItems(editor: Editor | null) { if (!editor) { return []; } @@ -220,6 +221,6 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag NumberedListItem(editor), QuoteItem(editor), TableItem(editor), - ImageItem(editor, uploadFile), + ImageItem(editor), ]; } diff --git a/packages/editor/src/core/extensions/core-without-props.ts b/packages/editor/src/core/extensions/core-without-props.ts index 26f9b2ca8e..1cedd51396 100644 --- a/packages/editor/src/core/extensions/core-without-props.ts +++ b/packages/editor/src/core/extensions/core-without-props.ts @@ -11,7 +11,7 @@ import { CustomCodeInlineExtension } from "./code-inline"; import { CustomLinkExtension } from "./custom-link"; import { CustomHorizontalRule } from "./horizontal-rule"; import { ImageExtensionWithoutProps } from "./image"; -import { ImageBlockWithoutProps } from "./image/image-block-without-props"; +import { CustomImageComponentWithoutProps } from "./image/image-component-without-props"; import { IssueWidgetWithoutProps } from "./issue-embed/issue-embed-without-props"; import { CustomMentionWithoutProps } from "./mentions/mentions-without-props"; import { CustomQuoteExtension } from "./quote"; @@ -62,7 +62,7 @@ export const CoreEditorExtensionsWithoutProps = [ class: "rounded-md", }, }), - ImageBlockWithoutProps(), + CustomImageComponentWithoutProps(), TiptapUnderline, TextStyle, TaskList.configure({ diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index de979e5177..13093bfa33 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -30,7 +30,7 @@ import { import { isValidHttpUrl } from "@/helpers/common"; // types import { DeleteImage, IMentionHighlight, IMentionSuggestion, RestoreImage, UploadImage } from "@/types"; -import { ImageBlock } from "./image-block"; +import { CustomImageComponent } from "./image-block"; type TArguments = { enableHistory: boolean; @@ -108,7 +108,7 @@ export const CoreEditorExtensions = ({ class: "rounded-md", }, }), - ImageBlock({ + CustomImageComponent({ deleteFile, restoreFile, uploadFile, diff --git a/packages/editor/src/core/extensions/image-block/components/image-block-view.tsx b/packages/editor/src/core/extensions/image-block/components/image-block-view.tsx index 96dd1891b5..87db97debe 100644 --- a/packages/editor/src/core/extensions/image-block/components/image-block-view.tsx +++ b/packages/editor/src/core/extensions/image-block/components/image-block-view.tsx @@ -1,6 +1,7 @@ import React, { useRef, useState, useCallback, useEffect } from "react"; import { Node as ProsemirrorNode } from "@tiptap/pm/model"; import { Editor } from "@tiptap/react"; +import { ImageShimmer } from "./image-loader"; interface ImageBlockViewProps { editor: Editor; @@ -27,6 +28,7 @@ export const ImageBlockView: React.FC = (props) => { const imageRef = useRef(null); const isResizing = useRef(false); const aspectRatio = useRef(1); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (imageRef.current) { @@ -39,6 +41,7 @@ export const ImageBlockView: React.FC = (props) => { const newHeight = newWidth / aspectRatio.current; setSize({ width: `${newWidth}px`, height: `${newHeight}px` }); } + setIsLoading(false); }; } }, [src, width, height]); @@ -97,6 +100,7 @@ export const ImageBlockView: React.FC = (props) => { return (
+ {isLoading ? : null} = (props) => { style={{ width: size.width, height: size.height, + display: isLoading ? "none" : "block", }} /> {isSelected && ( diff --git a/packages/editor/src/core/extensions/image-block/components/image-loader.tsx b/packages/editor/src/core/extensions/image-block/components/image-loader.tsx new file mode 100644 index 0000000000..52c62493aa --- /dev/null +++ b/packages/editor/src/core/extensions/image-block/components/image-loader.tsx @@ -0,0 +1,3 @@ +export const ImageShimmer: React.FC<{ width: string; height: string }> = ({ width, height }) => ( +
+); diff --git a/packages/editor/src/core/extensions/image-block/image-block.ts b/packages/editor/src/core/extensions/image-block/image-block.ts index d297368230..316c876f72 100644 --- a/packages/editor/src/core/extensions/image-block/image-block.ts +++ b/packages/editor/src/core/extensions/image-block/image-block.ts @@ -1,4 +1,4 @@ -import { mergeAttributes, Range } from "@tiptap/core"; +import { mergeAttributes } from "@tiptap/core"; import { Image } from "@tiptap/extension-image"; import { ReactNodeViewRenderer } from "@tiptap/react"; import { v4 as uuidv4 } from "uuid"; @@ -9,19 +9,14 @@ import { ImageUpload } from "../image-upload/view"; declare module "@tiptap/core" { interface Commands { - imageBlock: { - setImageBlock: (attributes: { src: string; width?: number; height?: number }) => ReturnType; - setImageBlockAt: (attributes: { - src: string; - pos: number | Range; - width?: number; - height?: number; - }) => ReturnType; + imageComponent: { + setImageUpload: ({ file, pos, event }: { file?: File; pos?: number; event: "insert" | "drop" }) => ReturnType; + uploadImage: (file: File) => () => Promise | undefined; }; } } -export const ImageBlock = ({ +export const CustomImageComponent = ({ uploadFile, // deleteFile, // restoreFile, @@ -33,7 +28,7 @@ export const ImageBlock = ({ cancelUploadImage?: () => void; }) => Image.extend<{}, UploadImageExtensionStorage>({ - name: "imageBlock", + name: "imageComponent", group: "inline", draggable: true, @@ -64,13 +59,13 @@ export const ImageBlock = ({ parseHTML() { return [ { - tag: "image-block", + tag: "image-component", }, ]; }, renderHTML({ HTMLAttributes }) { - return ["image-block", mergeAttributes(HTMLAttributes)]; + return ["image-component", mergeAttributes(HTMLAttributes)]; }, addStorage() { @@ -81,31 +76,17 @@ export const ImageBlock = ({ addCommands() { return { - setImageBlock: - (attrs) => - ({ commands }) => - commands.insertContent({ - type: this.name, - attrs: { src: attrs.src }, - }), - setImageBlockAt: - (attrs) => - ({ commands }) => - commands.insertContentAt(attrs.pos, { - type: this.name, - attrs: { src: attrs.src }, - }), setImageUpload: - (props: { file?: File; pos?: number; event: "insert" | "replace" | "drop" }) => + (props: { file?: File; pos?: number; event: "insert" | "drop" }) => ({ commands }) => { const fileId = uuidv4(); - if (props?.file && props?.event === "drop") { - (this.editor.storage.imageBlock as UploadImageExtensionStorage).fileMap.set(fileId, { + if (props?.event === "drop" && props.file) { + (this.editor.storage.imageComponent as UploadImageExtensionStorage).fileMap.set(fileId, { file: props.file, event: props.event, }); - } else if (props.event !== "drop") { - (this.editor.storage.imageBlock as UploadImageExtensionStorage).fileMap.set(fileId, { + } else if (props.event === "insert") { + (this.editor.storage.imageComponent as UploadImageExtensionStorage).fileMap.set(fileId, { event: props.event, }); } @@ -115,6 +96,12 @@ export const ImageBlock = ({ "data-file": props?.file ? `data-file="${props.file}"` : "", }; + if (props.pos) { + return commands.insertContentAt(props.pos, { + type: this.name, + attrs: attributes, + }); + } return commands.insertContent({ type: this.name, attrs: attributes, @@ -134,4 +121,4 @@ export const ImageBlock = ({ inline: true, }); -export default ImageBlock; +export default CustomImageComponent; diff --git a/packages/editor/src/core/extensions/image-upload/image-upload.ts b/packages/editor/src/core/extensions/image-upload/image-upload.ts index f3913bc421..141eb2d282 100644 --- a/packages/editor/src/core/extensions/image-upload/image-upload.ts +++ b/packages/editor/src/core/extensions/image-upload/image-upload.ts @@ -3,30 +3,11 @@ import { v4 as uuidv4 } from "uuid"; import { DeleteImage, RestoreImage, UploadImage } from "@/types"; import { ImageUpload as ImageUploadComponent } from "./view/image-upload"; -declare module "@tiptap/core" { - interface Commands { - imageUpload: { - setImageUpload: ({ - file, - pos, - event, - }: { - file?: File; - pos?: number; - event: "insert" | "replace" | "drop"; - }) => ReturnType; - uploadImage: (file: File) => () => Promise | undefined; - restoreImage: (assetUrlWithWorkspaceId: string) => Promise; - deleteImage: (assetUrlWithWorkspaceId: string) => Promise; - }; - } -} - export interface UploadImageExtensionStorage { fileMap: Map; } -export type UploadEntity = ({ event: "insert" } | { event: "replace" } | { event: "drop"; file: File }) & { +export type UploadEntity = ({ event: "insert" } | { event: "drop"; file: File }) & { pos?: number; }; @@ -91,16 +72,16 @@ export const ImageUpload = ({ addCommands() { return { setImageUpload: - (props: { file?: File; pos?: number; event: "insert" | "replace" | "drop" }) => + (props: { file?: File; pos?: number; event: "insert" | "drop" }) => ({ commands }) => { const fileId = uuidv4(); if (props?.file && props?.event === "drop") { - (this.editor.storage.imageBlock as UploadImageExtensionStorage).fileMap.set(fileId, { + (this.editor.storage.imageComponent as UploadImageExtensionStorage).fileMap.set(fileId, { file: props.file, event: props.event, }); } else if (props.event !== "drop") { - (this.editor.storage.imageBlock as UploadImageExtensionStorage).fileMap.set(fileId, { + (this.editor.storage.imageComponent as UploadImageExtensionStorage).fileMap.set(fileId, { event: props.event, }); } diff --git a/packages/editor/src/core/extensions/image-upload/view/image-upload.tsx b/packages/editor/src/core/extensions/image-upload/view/image-upload.tsx index 7914f7129d..6a2eb066b9 100644 --- a/packages/editor/src/core/extensions/image-upload/view/image-upload.tsx +++ b/packages/editor/src/core/extensions/image-upload/view/image-upload.tsx @@ -24,7 +24,7 @@ export const ImageUpload: React.FC = ({ getPos, editor, node, const [isUploaded, setIsUploaded] = useState(!!node.attrs.src); const id = node.attrs.id as string; - const editorStorage = editor.storage.imageBlock as UploadImageExtensionStorage | undefined; + const editorStorage = editor.storage.imageComponent as UploadImageExtensionStorage | undefined; const getUploadEntity = useCallback( (): UploadEntity | undefined => editorStorage?.fileMap.get(id), diff --git a/packages/editor/src/core/extensions/image/image-block-without-props.tsx b/packages/editor/src/core/extensions/image/image-component-without-props.tsx similarity index 58% rename from packages/editor/src/core/extensions/image/image-block-without-props.tsx rename to packages/editor/src/core/extensions/image/image-component-without-props.tsx index 7feb1b09ac..ceb6665eca 100644 --- a/packages/editor/src/core/extensions/image/image-block-without-props.tsx +++ b/packages/editor/src/core/extensions/image/image-component-without-props.tsx @@ -1,24 +1,10 @@ -import { mergeAttributes, Range } from "@tiptap/core"; +import { mergeAttributes } from "@tiptap/core"; import { Image } from "@tiptap/extension-image"; import { UploadImageExtensionStorage } from "../image-upload"; -declare module "@tiptap/core" { - interface Commands { - imageBlock: { - setImageBlock: (attributes: { src: string; width?: number; height?: number }) => ReturnType; - setImageBlockAt: (attributes: { - src: string; - pos: number | Range; - width?: number; - height?: number; - }) => ReturnType; - }; - } -} - -export const ImageBlockWithoutProps = () => +export const CustomImageComponentWithoutProps = () => Image.extend<{}, UploadImageExtensionStorage>({ - name: "imageBlock", + name: "imageComponent", group: "inline", draggable: true, @@ -49,13 +35,13 @@ export const ImageBlockWithoutProps = () => parseHTML() { return [ { - tag: "image-block", + tag: "image-component", }, ]; }, renderHTML({ HTMLAttributes }) { - return ["image-block", mergeAttributes(HTMLAttributes)]; + return ["image-component", mergeAttributes(HTMLAttributes)]; }, addStorage() { @@ -67,4 +53,4 @@ export const ImageBlockWithoutProps = () => inline: true, }); -export default ImageBlockWithoutProps; +export default CustomImageComponentWithoutProps; diff --git a/packages/editor/src/styles/drag-drop.css b/packages/editor/src/styles/drag-drop.css index 848dc7fe48..72d190f32f 100644 --- a/packages/editor/src/styles/drag-drop.css +++ b/packages/editor/src/styles/drag-drop.css @@ -64,7 +64,7 @@ pointer-events: none; } - &.node-imageUpload { + &.node-imageComponent { --horizontal-offset: 0px; &::after { @@ -104,7 +104,7 @@ ol > li:nth-child(n + 100).ProseMirror-selectednode:not(.dragging)::after { margin-left: -35px; } -.ProseMirror node-imageBlock { +.ProseMirror node-imageComponent { transition: filter 0.1s ease-in-out; cursor: pointer;