Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ const DocumentEditor = (props: IDocumentEditor) => {

return (
<PageRenderer
tabIndex={tabIndex}
editor={editor}
editorContainerClassName={editorContainerClassNames}
hideDragHandle={hideDragHandleOnMouseLeave}
id={id}
tabIndex={tabIndex}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +31,7 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => {
containerClassName,
editorClassName = "",
embedHandler,
id,
initialValue,
forwardedRef,
tabIndex,
Expand Down Expand Up @@ -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) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const EditorWrapper: React.FC<Props> = (props) => {
editorClassName = "",
extensions,
hideDragHandleOnMouseLeave,
id = "",
id,
initialValue,
fileHandler,
forwardedRef,
Expand Down Expand Up @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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>
);
Expand Down
135 changes: 72 additions & 63 deletions packages/editor/src/core/extensions/image/image-resize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,84 @@ 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 selection = editor.state.selection;
const imageElement = getImageElement(id);

// 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");
if (!imageElement) return;

editor.commands.setImage({
src: imageInfo.src,
width: width,
height: height,
} as any);
editor.commands.setNodeSelection(selection.from);
}
};
const selection = editor.state.selection;

const [aspectRatio, setAspectRatio] = useState(1);
// 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);
}
}}
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`;
<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;
}
}}
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;
}}
/>
);
};
3 changes: 2 additions & 1 deletion packages/editor/src/core/extensions/mentions/extension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/core/extensions/slash-commands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/core/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]>;
Expand All @@ -52,6 +52,7 @@ export interface IReadOnlyEditorProps {
containerClassName?: string;
editorClassName?: string;
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
id: string;
initialValue: string;
mentionHandler: {
highlights: () => Promise<IMentionHighlight[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 === "" ||
Expand Down
Loading