[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:
parent
6bb534dabc
commit
518327e380
22 changed files with 129 additions and 88 deletions
|
|
@ -78,10 +78,11 @@ const DocumentEditor = (props: IDocumentEditor) => {
|
|||
|
||||
return (
|
||||
<PageRenderer
|
||||
tabIndex={tabIndex}
|
||||
editor={editor}
|
||||
editorContainerClassName={editorContainerClassNames}
|
||||
hideDragHandle={hideDragHandleOnMouseLeave}
|
||||
id={id}
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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", {
|
||||
|
|
|
|||
|
|
@ -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[]>;
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 === "" ||
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
|
|||
/>
|
||||
) : (
|
||||
<RichTextReadOnlyEditor
|
||||
id={issueId}
|
||||
initialValue={localIssueDescription.description_html ?? ""}
|
||||
containerClassName={containerClassName}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue