[WEB-1974] fix: images getting replaced on resize (#5233)

* fix: image resizer error

* refactor: created common function to get the active image element

* fix: build errors
This commit is contained in:
Aaryan Khandelwal 2024-07-30 14:58:40 +05:30 committed by GitHub
parent 6bb534dabc
commit 518327e380
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 129 additions and 88 deletions

View file

@ -78,10 +78,11 @@ const DocumentEditor = (props: IDocumentEditor) => {
return (
<PageRenderer
tabIndex={tabIndex}
editor={editor}
editorContainerClassName={editorContainerClassNames}
hideDragHandle={hideDragHandleOnMouseLeave}
id={id}
tabIndex={tabIndex}
/>
);
};

View file

@ -21,11 +21,12 @@ type IPageRenderer = {
editor: Editor;
editorContainerClassName: string;
hideDragHandle?: () => void;
id: string;
tabIndex?: number;
};
export const PageRenderer = (props: IPageRenderer) => {
const { tabIndex, editor, hideDragHandle, editorContainerClassName } = props;
const { editor, editorContainerClassName, hideDragHandle, id, tabIndex } = props;
// states
const [linkViewProps, setLinkViewProps] = useState<LinkViewProps>();
const [isOpen, setIsOpen] = useState(false);
@ -130,10 +131,11 @@ export const PageRenderer = (props: IPageRenderer) => {
<div className="frame-renderer flex-grow w-full -mx-5" onMouseOver={handleLinkHover}>
<EditorContainer
editor={editor}
hideDragHandle={hideDragHandle}
editorContainerClassName={editorContainerClassName}
hideDragHandle={hideDragHandle}
id={id}
>
<EditorContentWrapper tabIndex={tabIndex} editor={editor} />
<EditorContentWrapper editor={editor} id={id} tabIndex={tabIndex} />
{editor && editor.isEditable && <BlockMenu editor={editor} />}
</EditorContainer>
</div>

View file

@ -13,6 +13,7 @@ import { TEmbedConfig } from "@/plane-editor/types";
import { EditorReadOnlyRefApi, IMentionHighlight } from "@/types";
interface IDocumentReadOnlyEditor {
id: string;
initialValue: string;
containerClassName: string;
editorClassName?: string;
@ -30,6 +31,7 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => {
containerClassName,
editorClassName = "",
embedHandler,
id,
initialValue,
forwardedRef,
tabIndex,
@ -58,7 +60,9 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => {
containerClassName,
});
return <PageRenderer tabIndex={tabIndex} editor={editor} editorContainerClassName={editorContainerClassName} />;
return (
<PageRenderer editor={editor} editorContainerClassName={editorContainerClassName} id={id} tabIndex={tabIndex} />
);
};
const DocumentReadOnlyEditorWithRef = forwardRef<EditorReadOnlyRefApi, IDocumentReadOnlyEditor>((props, ref) => (

View file

@ -4,14 +4,15 @@ import { Editor } from "@tiptap/react";
import { cn } from "@/helpers/common";
interface EditorContainerProps {
children: ReactNode;
editor: Editor | null;
editorContainerClassName: string;
children: ReactNode;
hideDragHandle?: () => void;
id: string;
}
export const EditorContainer: FC<EditorContainerProps> = (props) => {
const { editor, editorContainerClassName, hideDragHandle, children } = props;
const { children, editor, editorContainerClassName, hideDragHandle, id } = props;
const handleContainerClick = () => {
if (!editor) return;
@ -54,7 +55,7 @@ export const EditorContainer: FC<EditorContainerProps> = (props) => {
return (
<div
id="editor-container"
id={`editor-container-${id}`}
onClick={handleContainerClick}
onMouseLeave={hideDragHandle}
className={cn(

View file

@ -4,18 +4,19 @@ import { Editor, EditorContent } from "@tiptap/react";
import { ImageResizer } from "@/extensions/image";
interface EditorContentProps {
editor: Editor | null;
children?: ReactNode;
editor: Editor | null;
id: string;
tabIndex?: number;
}
export const EditorContentWrapper: FC<EditorContentProps> = (props) => {
const { editor, tabIndex, children } = props;
const { editor, children, id, tabIndex } = props;
return (
<div tabIndex={tabIndex} onFocus={() => editor?.chain().focus(undefined, { scrollIntoView: false }).run()}>
<EditorContent editor={editor} />
{editor?.isActive("image") && editor?.isEditable && <ImageResizer editor={editor} />}
{editor?.isActive("image") && editor?.isEditable && <ImageResizer editor={editor} id={id} />}
{children}
</div>
);

View file

@ -21,7 +21,7 @@ export const EditorWrapper: React.FC<Props> = (props) => {
editorClassName = "",
extensions,
hideDragHandleOnMouseLeave,
id = "",
id,
initialValue,
fileHandler,
forwardedRef,
@ -57,13 +57,14 @@ export const EditorWrapper: React.FC<Props> = (props) => {
return (
<EditorContainer
hideDragHandle={hideDragHandleOnMouseLeave}
editor={editor}
editorContainerClassName={editorContainerClassName}
id={id}
hideDragHandle={hideDragHandleOnMouseLeave}
>
{children?.(editor)}
<div className="flex flex-col">
<EditorContentWrapper tabIndex={tabIndex} editor={editor} />
<EditorContentWrapper editor={editor} id={id} tabIndex={tabIndex} />
</div>
</EditorContainer>
);

View file

@ -8,7 +8,7 @@ import { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
import { IReadOnlyEditorProps } from "@/types";
export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
const { containerClassName, editorClassName = "", initialValue, forwardedRef, mentionHandler } = props;
const { containerClassName, editorClassName = "", id, initialValue, forwardedRef, mentionHandler } = props;
const editor = useReadOnlyEditor({
initialValue,
@ -24,9 +24,9 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
if (!editor) return null;
return (
<EditorContainer editor={editor} editorContainerClassName={editorContainerClassName}>
<EditorContainer editor={editor} editorContainerClassName={editorContainerClassName} id={id}>
<div className="flex flex-col">
<EditorContentWrapper editor={editor} />
<EditorContentWrapper editor={editor} id={id} />
</div>
</EditorContainer>
);

View file

@ -2,75 +2,84 @@ import { useState } from "react";
import { Editor } from "@tiptap/react";
import Moveable from "react-moveable";
export const ImageResizer = ({ editor }: { editor: Editor }) => {
const updateMediaSize = () => {
const imageInfo = document.querySelector(".ProseMirror-selectednode") as HTMLImageElement;
if (imageInfo) {
const selection = editor.state.selection;
type Props = {
editor: Editor;
id: string;
};
// Use the style width/height if available, otherwise fall back to the element's natural width/height
const width = imageInfo.style.width
? Number(imageInfo.style.width.replace("px", ""))
: imageInfo.getAttribute("width");
const height = imageInfo.style.height
? Number(imageInfo.style.height.replace("px", ""))
: imageInfo.getAttribute("height");
editor.commands.setImage({
src: imageInfo.src,
width: width,
height: height,
} as any);
editor.commands.setNodeSelection(selection.from);
}
};
const getImageElement = (editorId: string): HTMLImageElement | null =>
document.querySelector(`#editor-container-${editorId}.active-editor .ProseMirror-selectednode`);
export const ImageResizer = (props: Props) => {
const { editor, id } = props;
// states
const [aspectRatio, setAspectRatio] = useState(1);
const updateMediaSize = () => {
const imageElement = getImageElement(id);
if (!imageElement) return;
const selection = editor.state.selection;
// Use the style width/height if available, otherwise fall back to the element's natural width/height
const width = imageElement.style.width
? Number(imageElement.style.width.replace("px", ""))
: imageElement.getAttribute("width");
const height = imageElement.style.height
? Number(imageElement.style.height.replace("px", ""))
: imageElement.getAttribute("height");
editor.commands.setImage({
src: imageElement.src,
width: width,
height: height,
} as any);
editor.commands.setNodeSelection(selection.from);
};
return (
<>
<Moveable
target={document.querySelector(".active-editor .ProseMirror-selectednode") as HTMLElement}
container={null}
origin={false}
edge={false}
throttleDrag={0}
keepRatio
resizable
throttleResize={0}
onResizeStart={() => {
const imageInfo = document.querySelector(".active-editor .ProseMirror-selectednode") as HTMLImageElement;
if (imageInfo) {
const originalWidth = Number(imageInfo.width);
const originalHeight = Number(imageInfo.height);
setAspectRatio(originalWidth / originalHeight);
<Moveable
target={getImageElement(id)}
container={null}
origin={false}
edge={false}
throttleDrag={0}
keepRatio
resizable
throttleResize={0}
onResizeStart={() => {
const imageElement = getImageElement(id);
if (imageElement) {
const originalWidth = Number(imageElement.width);
const originalHeight = Number(imageElement.height);
setAspectRatio(originalWidth / originalHeight);
}
}}
onResize={({ target, width, height, delta }) => {
if (delta[0] || delta[1]) {
let newWidth, newHeight;
if (delta[0]) {
// Width change detected
newWidth = Math.max(width, 100);
newHeight = newWidth / aspectRatio;
} else if (delta[1]) {
// Height change detected
newHeight = Math.max(height, 100);
newWidth = newHeight * aspectRatio;
}
}}
onResize={({ target, width, height, delta }) => {
if (delta[0] || delta[1]) {
let newWidth, newHeight;
if (delta[0]) {
// Width change detected
newWidth = Math.max(width, 100);
newHeight = newWidth / aspectRatio;
} else if (delta[1]) {
// Height change detected
newHeight = Math.max(height, 100);
newWidth = newHeight * aspectRatio;
}
target.style.width = `${newWidth}px`;
target.style.height = `${newHeight}px`;
}
}}
onResizeEnd={() => {
updateMediaSize();
}}
scalable
renderDirections={["se"]}
onScale={({ target, transform }) => {
target.style.transform = transform;
}}
/>
</>
target.style.width = `${newWidth}px`;
target.style.height = `${newHeight}px`;
}
}}
onResizeEnd={() => {
updateMediaSize();
}}
scalable
renderDirections={["se"]}
onScale={({ target, transform }) => {
target.style.transform = transform;
}}
/>
);
};

View file

@ -91,7 +91,8 @@ export const CustomMention = ({
// @ts-expect-error - Tippy types are incorrect
popup = tippy("body", {
getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"),
appendTo: () =>
document.querySelector(".active-editor") ?? document.querySelector('[id^="editor-container"]'),
content: component.element,
showOnCreate: true,
interactive: true,

View file

@ -374,7 +374,8 @@ const renderItems = () => {
editor: props.editor,
});
const tippyContainer = document.querySelector(".active-editor") ?? document.querySelector("#editor-container");
const tippyContainer =
document.querySelector(".active-editor") ?? document.querySelector('[id^="editor-container"]');
// @ts-expect-error Tippy overloads are messed up
popup = tippy("body", {

View file

@ -29,7 +29,7 @@ export interface IEditorProps {
editorClassName?: string;
fileHandler: TFileHandler;
forwardedRef?: React.MutableRefObject<EditorRefApi | null>;
id?: string;
id: string;
initialValue: string;
mentionHandler: {
highlights: () => Promise<IMentionHighlight[]>;
@ -52,6 +52,7 @@ export interface IReadOnlyEditorProps {
containerClassName?: string;
editorClassName?: string;
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
id: string;
initialValue: string;
mentionHandler: {
highlights: () => Promise<IMentionHighlight[]>;