Skip to content

Commit

Permalink
fix: image insertion at pos and loading effect added
Browse files Browse the repository at this point in the history
  • Loading branch information
Palanikannan1437 committed Sep 12, 2024
1 parent 56a0b9f commit d8308cf
Show file tree
Hide file tree
Showing 10 changed files with 50 additions and 87 deletions.
9 changes: 5 additions & 4 deletions packages/editor/src/core/components/menus/menu-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 [];
}
Expand All @@ -220,6 +221,6 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag
NumberedListItem(editor),
QuoteItem(editor),
TableItem(editor),
ImageItem(editor, uploadFile),
ImageItem(editor),
];
}
4 changes: 2 additions & 2 deletions packages/editor/src/core/extensions/core-without-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -62,7 +62,7 @@ export const CoreEditorExtensionsWithoutProps = [
class: "rounded-md",
},
}),
ImageBlockWithoutProps(),
CustomImageComponentWithoutProps(),
TiptapUnderline,
TextStyle,
TaskList.configure({
Expand Down
4 changes: 2 additions & 2 deletions packages/editor/src/core/extensions/extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -108,7 +108,7 @@ export const CoreEditorExtensions = ({
class: "rounded-md",
},
}),
ImageBlock({
CustomImageComponent({
deleteFile,
restoreFile,
uploadFile,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,6 +28,7 @@ export const ImageBlockView: React.FC<ImageBlockViewProps> = (props) => {
const imageRef = useRef<HTMLImageElement>(null);
const isResizing = useRef(false);
const aspectRatio = useRef(1);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
if (imageRef.current) {
Expand All @@ -39,6 +41,7 @@ export const ImageBlockView: React.FC<ImageBlockViewProps> = (props) => {
const newHeight = newWidth / aspectRatio.current;
setSize({ width: `${newWidth}px`, height: `${newHeight}px` });
}
setIsLoading(false);
};
}
}, [src, width, height]);
Expand Down Expand Up @@ -97,6 +100,7 @@ export const ImageBlockView: React.FC<ImageBlockViewProps> = (props) => {

return (
<div ref={containerRef} className="relative inline-block" onMouseDown={handleMouseDown} data-drag-handle>
{isLoading ? <ImageShimmer width={size.width} height={size.height} /> : null}
<img
ref={imageRef}
src={src}
Expand All @@ -105,6 +109,7 @@ export const ImageBlockView: React.FC<ImageBlockViewProps> = (props) => {
style={{
width: size.width,
height: size.height,
display: isLoading ? "none" : "block",
}}
/>
{isSelected && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ImageShimmer: React.FC<{ width: string; height: string }> = ({ width, height }) => (
<div className="animate-pulse bg-custom-background-80 rounded-md" style={{ width, height }} />
);
53 changes: 20 additions & 33 deletions packages/editor/src/core/extensions/image-block/image-block.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -9,19 +9,14 @@ import { ImageUpload } from "../image-upload/view";

declare module "@tiptap/core" {
interface Commands<ReturnType> {
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<string> | undefined;
};
}
}

export const ImageBlock = ({
export const CustomImageComponent = ({
uploadFile,
// deleteFile,
// restoreFile,
Expand All @@ -33,7 +28,7 @@ export const ImageBlock = ({
cancelUploadImage?: () => void;
}) =>
Image.extend<{}, UploadImageExtensionStorage>({
name: "imageBlock",
name: "imageComponent",
group: "inline",
draggable: true,

Expand Down Expand Up @@ -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() {
Expand All @@ -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,
});
}
Expand All @@ -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,
Expand All @@ -134,4 +121,4 @@ export const ImageBlock = ({
inline: true,
});

export default ImageBlock;
export default CustomImageComponent;
27 changes: 4 additions & 23 deletions packages/editor/src/core/extensions/image-upload/image-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReturnType> {
imageUpload: {
setImageUpload: ({
file,
pos,
event,
}: {
file?: File;
pos?: number;
event: "insert" | "replace" | "drop";
}) => ReturnType;
uploadImage: (file: File) => () => Promise<string> | undefined;
restoreImage: (assetUrlWithWorkspaceId: string) => Promise<ReturnType>;
deleteImage: (assetUrlWithWorkspaceId: string) => Promise<ReturnType>;
};
}
}

export interface UploadImageExtensionStorage {
fileMap: Map<string, UploadEntity>;
}

export type UploadEntity = ({ event: "insert" } | { event: "replace" } | { event: "drop"; file: File }) & {
export type UploadEntity = ({ event: "insert" } | { event: "drop"; file: File }) & {
pos?: number;
};

Expand Down Expand Up @@ -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,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const ImageUpload: React.FC<ImageUploadProps> = ({ 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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ReturnType> {
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,

Expand Down Expand Up @@ -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() {
Expand All @@ -67,4 +53,4 @@ export const ImageBlockWithoutProps = () =>
inline: true,
});

export default ImageBlockWithoutProps;
export default CustomImageComponentWithoutProps;
4 changes: 2 additions & 2 deletions packages/editor/src/styles/drag-drop.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
pointer-events: none;
}

&.node-imageUpload {
&.node-imageComponent {

Check notice on line 67 in packages/editor/src/styles/drag-drop.css

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

packages/editor/src/styles/drag-drop.css#L67

(selector) => `Expected class selector "${selector}" to be kebab-case`
--horizontal-offset: 0px;

&::after {
Expand Down Expand Up @@ -104,7 +104,7 @@ ol > li:nth-child(n + 100).ProseMirror-selectednode:not(.dragging)::after {
margin-left: -35px;
}

.ProseMirror node-imageBlock {
.ProseMirror node-imageComponent {

Check notice on line 107 in packages/editor/src/styles/drag-drop.css

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

packages/editor/src/styles/drag-drop.css#L107

(selector) => `Expected class selector "${selector}" to be kebab-case`

Check notice on line 107 in packages/editor/src/styles/drag-drop.css

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

packages/editor/src/styles/drag-drop.css#L107

Expected "node-imageComponent" to be "node-imagecomponent" (selector-type-case)
transition: filter 0.1s ease-in-out;
cursor: pointer;

Expand Down

0 comments on commit d8308cf

Please sign in to comment.