[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,35 +2,45 @@ import { useState } from "react";
import { Editor } from "@tiptap/react";
import Moveable from "react-moveable";
export const ImageResizer = ({ editor }: { editor: Editor }) => {
type Props = {
editor: Editor;
id: string;
};
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 imageInfo = document.querySelector(".ProseMirror-selectednode") as HTMLImageElement;
if (imageInfo) {
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 = 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");
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: imageInfo.src,
src: imageElement.src,
width: width,
height: height,
} as any);
editor.commands.setNodeSelection(selection.from);
}
};
const [aspectRatio, setAspectRatio] = useState(1);
return (
<>
<Moveable
target={document.querySelector(".active-editor .ProseMirror-selectednode") as HTMLElement}
target={getImageElement(id)}
container={null}
origin={false}
edge={false}
@ -39,10 +49,10 @@ export const ImageResizer = ({ editor }: { editor: Editor }) => {
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);
const imageElement = getImageElement(id);
if (imageElement) {
const originalWidth = Number(imageElement.width);
const originalHeight = Number(imageElement.height);
setAspectRatio(originalWidth / originalHeight);
}
}}
@ -71,6 +81,5 @@ export const ImageResizer = ({ editor }: { editor: Editor }) => {
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[]>;

View file

@ -72,6 +72,7 @@ export const AddComment: React.FC<Props> = observer((props) => {
workspaceId={workspaceID?.toString() ?? ""}
workspaceSlug={workspaceSlug?.toString() ?? ""}
ref={editorRef}
id="peek-overview-add-comment"
initialValue={
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
? watch("comment_html")

View file

@ -105,6 +105,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug?.toString() ?? ""}
onEnterKeyPress={() => handleSubmit(handleCommentUpdate)()}
ref={editorRef}
id={comment.id}
initialValue={value}
value={null}
onChange={(comment_json, comment_html) => onChange(comment_html)}
@ -132,7 +133,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
</div>
</form>
<div className={`${isEditing ? "hidden" : ""}`}>
<LiteTextReadOnlyEditor ref={showEditorRef} initialValue={comment.comment_html} />
<LiteTextReadOnlyEditor ref={showEditorRef} id={comment.id} initialValue={comment.comment_html} />
<CommentReactions anchor={anchor} commentId={comment.id} />
</div>
</div>

View file

@ -26,6 +26,7 @@ export const PeekOverviewIssueDetails: React.FC<Props> = observer((props) => {
<h4 className="break-words text-2xl font-medium">{issueDetails.name}</h4>
{description !== "" && description !== "<p></p>" && (
<RichTextReadOnlyEditor
id={issueDetails.id}
initialValue={
!description ||
description === "" ||

View file

@ -203,13 +203,22 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
{prompt && (
<div className="text-sm">
Content:
<RichTextReadOnlyEditor initialValue={prompt} containerClassName="-m-3" ref={editorRef} />
<RichTextReadOnlyEditor
id="ai-assistant-content"
initialValue={prompt}
containerClassName="-m-3"
ref={editorRef}
/>
</div>
)}
{response !== "" && (
<div className="page-block-section max-h-[8rem] text-sm">
Response:
<RichTextReadOnlyEditor initialValue={`<p>${response}</p>`} ref={responseRef} />
<RichTextReadOnlyEditor
id="ai-assistant-response"
initialValue={`<p>${response}</p>`}
ref={responseRef}
/>
</div>
)}
{invalidResponse && (

View file

@ -42,6 +42,7 @@ export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props
return (
<RichTextEditor
id="inbox-modal-editor"
initialValue={!data?.description_html || data?.description_html === "" ? "<p></p>" : data?.description_html}
ref={editorRef}
workspaceSlug={workspaceSlug}

View file

@ -118,6 +118,7 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
/>
) : (
<RichTextReadOnlyEditor
id={issueId}
initialValue={localIssueDescription.description_html ?? ""}
containerClassName={containerClassName}
/>

View file

@ -143,6 +143,7 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
projectId={projectId}
workspaceSlug={workspaceSlug}
ref={editorRef}
id={comment.id}
initialValue={watch("comment_html") ?? ""}
value={null}
onChange={(comment_json, comment_html) => setValue("comment_html", comment_html)}
@ -190,7 +191,7 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
)}
</div>
)}
<LiteTextReadOnlyEditor ref={showEditorRef} initialValue={comment.comment_html ?? ""} />
<LiteTextReadOnlyEditor ref={showEditorRef} id={comment.id} initialValue={comment.comment_html ?? ""} />
<IssueCommentReaction
workspaceSlug={workspaceSlug}

View file

@ -460,6 +460,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
control={control}
render={({ field: { value, onChange } }) => (
<RichTextEditor
id="issue-modal-editor"
initialValue={value ?? ""}
value={data.description_html}
workspaceSlug={workspaceSlug?.toString() as string}

View file

@ -153,6 +153,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
) : (
<DocumentReadOnlyEditorWithRef
ref={readOnlyEditorRef}
id={pageId}
initialValue={pageDescription ?? "<p></p>"}
handleEditorReady={handleReadOnlyEditorReady}
containerClassName="p-0 pb-64 border-none"

View file

@ -68,6 +68,7 @@ export const ActivityList: React.FC<Props> = observer((props) => {
</div>
<div className="issue-comments-section p-0">
<RichTextReadOnlyEditor
id={activityItem.id}
initialValue={
activityItem?.new_value !== ""
? (activityItem.new_value?.toString() as string)

View file

@ -97,6 +97,7 @@ export const ProfileActivityListPage: React.FC<Props> = observer((props) => {
</div>
<div className="issue-comments-section p-0">
<RichTextReadOnlyEditor
id={activityItem.id}
initialValue={
activityItem?.new_value !== "" ? activityItem.new_value : activityItem.old_value
}