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
@@ -1,5 +1,5 @@
import { Extensions } from "@tiptap/core";
import React from "react";
import type { Extensions } from "@tiptap/core";
import React, { useMemo } from "react";
// plane imports
import { cn } from "@plane/utils";
// components
Expand All @@ -13,26 +13,32 @@ import { getEditorClassNames } from "@/helpers/common";
// hooks
import { useCollaborativeEditor } from "@/hooks/use-collaborative-editor";
// types
import { EditorRefApi, ICollaborativeDocumentEditorProps } from "@/types";
import type { EditorRefApi, ICollaborativeDocumentEditorProps } from "@/types";

const CollaborativeDocumentEditor: React.FC<ICollaborativeDocumentEditorProps> = (props) => {
const {
aiHandler,
bubbleMenuEnabled = true,
containerClassName,
documentLoaderClassName,
extensions: externalExtensions = [],
disabledExtensions,
displayConfig = DEFAULT_DISPLAY_CONFIG,
editable,
editorClassName = "",
editorProps,
embedHandler,
fileHandler,
flaggedExtensions,
forwardedRef,
handleEditorReady,
id,
dragDropEnabled = true,
isTouchDevice,
mentionHandler,
onAssetChange,
onChange,
onEditorFocus,
onTransaction,
placeholder,
realtimeConfig,
Expand All @@ -41,31 +47,39 @@ const CollaborativeDocumentEditor: React.FC<ICollaborativeDocumentEditorProps> =
user,
} = props;

const extensions: Extensions = [];
const extensions: Extensions = useMemo(() => {
const allExtensions = [...externalExtensions];

if (embedHandler?.issue) {
extensions.push(
WorkItemEmbedExtension({
widgetCallback: embedHandler.issue.widgetCallback,
})
);
}
if (embedHandler?.issue) {
allExtensions.push(
WorkItemEmbedExtension({
widgetCallback: embedHandler.issue.widgetCallback,
})
);
}

return allExtensions;
}, [externalExtensions, embedHandler.issue]);

// use document editor
const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeEditor({
disabledExtensions,
editable,
editorClassName,
editorProps,
embedHandler,
extensions,
fileHandler,
flaggedExtensions,
forwardedRef,
handleEditorReady,
id,
dragDropEnabled,
isTouchDevice,
mentionHandler,
onAssetChange,
onChange,
onEditorFocus,
onTransaction,
placeholder,
realtimeConfig,
Expand All @@ -87,9 +101,11 @@ const CollaborativeDocumentEditor: React.FC<ICollaborativeDocumentEditorProps> =
aiHandler={aiHandler}
bubbleMenuEnabled={bubbleMenuEnabled}
displayConfig={displayConfig}
documentLoaderClassName={documentLoaderClassName}
editor={editor}
editorContainerClassName={cn(editorContainerClassNames, "document-editor")}
id={id}
isTouchDevice={!!isTouchDevice}
isLoading={!hasServerSynced && !hasServerConnectionFailed}
tabIndex={tabIndex}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,28 @@ type Props = {
aiHandler?: TAIHandler;
bubbleMenuEnabled: boolean;
displayConfig: TDisplayConfig;
documentLoaderClassName?: string;
editor: Editor;
editorContainerClassName: string;
id: string;
isLoading?: boolean;
isTouchDevice: boolean;
tabIndex?: number;
};

export const PageRenderer = (props: Props) => {
const { aiHandler, bubbleMenuEnabled, displayConfig, editor, editorContainerClassName, id, isLoading, tabIndex } =
props;
const {
aiHandler,
bubbleMenuEnabled,
displayConfig,
documentLoaderClassName,
editor,
editorContainerClassName,
id,
isLoading,
isTouchDevice,
tabIndex,
} = props;

return (
<div
Expand All @@ -29,16 +41,17 @@ export const PageRenderer = (props: Props) => {
})}
>
{isLoading ? (
<DocumentContentLoader />
<DocumentContentLoader className={documentLoaderClassName} />
) : (
<EditorContainer
displayConfig={displayConfig}
editor={editor}
editorContainerClassName={editorContainerClassName}
id={id}
isTouchDevice={isTouchDevice}
>
<EditorContentWrapper editor={editor} id={id} tabIndex={tabIndex} />
{editor.isEditable && (
{editor.isEditable && !isTouchDevice && (
<div>
{bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}
<BlockMenu editor={editor} />
Expand Down
14 changes: 8 additions & 6 deletions packages/editor/src/core/components/editors/editor-container.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Editor } from "@tiptap/react";
import type { Editor } from "@tiptap/react";
import { FC, ReactNode, useRef } from "react";
// plane utils
import { cn } from "@plane/utils";
Expand All @@ -10,16 +10,18 @@ import { TDisplayConfig } from "@/types";
// components
import { LinkViewContainer } from "./link-view-container";

interface EditorContainerProps {
type Props = {
children: ReactNode;
displayConfig: TDisplayConfig;
editor: Editor;
editorContainerClassName: string;
id: string;
}
isTouchDevice: boolean;
};

export const EditorContainer: FC<EditorContainerProps> = (props) => {
const { children, displayConfig, editor, editorContainerClassName, id } = props;
export const EditorContainer: FC<Props> = (props) => {
const { children, displayConfig, editor, editorContainerClassName, id, isTouchDevice } = props;
// refs
const containerRef = useRef<HTMLDivElement>(null);

const handleContainerClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
Expand Down Expand Up @@ -94,7 +96,7 @@ export const EditorContainer: FC<EditorContainerProps> = (props) => {
)}
>
{children}
<LinkViewContainer editor={editor} containerRef={containerRef} />
{!isTouchDevice && <LinkViewContainer editor={editor} containerRef={containerRef} />}
</div>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ export const EditorWrapper: React.FC<Props> = (props) => {
displayConfig = DEFAULT_DISPLAY_CONFIG,
editable,
editorClassName = "",
editorProps,
extensions,
id,
initialValue,
isTouchDevice,
fileHandler,
flaggedExtensions,
forwardedRef,
mentionHandler,
onChange,
onEditorFocus,
onTransaction,
handleEditorReady,
autofocus,
Expand All @@ -44,15 +47,18 @@ export const EditorWrapper: React.FC<Props> = (props) => {
editable,
disabledExtensions,
editorClassName,
editorProps,
enableHistory: true,
extensions,
fileHandler,
flaggedExtensions,
forwardedRef,
id,
isTouchDevice,
initialValue,
mentionHandler,
onChange,
onEditorFocus,
onTransaction,
handleEditorReady,
autofocus,
Expand All @@ -75,6 +81,7 @@ export const EditorWrapper: React.FC<Props> = (props) => {
editor={editor}
editorContainerClassName={editorContainerClassName}
id={id}
isTouchDevice={!!isTouchDevice}
>
{children?.(editor)}
<div className="flex flex-col">
Expand Down
19 changes: 18 additions & 1 deletion packages/editor/src/core/components/menus/menu-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
MinusSquare,
Palette,
AlignCenter,
LinkIcon,
} from "lucide-react";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
Expand All @@ -30,6 +31,7 @@ import {
insertHorizontalRule,
insertImage,
insertTableCommand,
setLinkEditor,
setText,
setTextAlign,
toggleBackgroundColor,
Expand All @@ -44,6 +46,7 @@ import {
toggleTaskList,
toggleTextColor,
toggleUnderline,
unsetLinkEditor,
} from "@/helpers/editor-commands";
// types
import { TCommandWithProps, TEditorCommands } from "@/types";
Expand Down Expand Up @@ -189,7 +192,7 @@ export const ImageItem = (editor: Editor): EditorMenuItem<"image"> => ({
icon: ImageIcon,
});

export const HorizontalRuleItem = (editor: Editor) =>
export const HorizontalRuleItem = (editor: Editor): EditorMenuItem<"divider"> =>
({
key: "divider",
name: "Divider",
Expand All @@ -198,6 +201,19 @@ export const HorizontalRuleItem = (editor: Editor) =>
icon: MinusSquare,
}) as const;

export const LinkItem = (editor: Editor): EditorMenuItem<"link"> =>
({
key: "link",
name: "Link",
isActive: () => editor?.isActive("link"),
command: (props) => {
if (!props) return;
if (props.url) setLinkEditor(editor, props.url, props.text);
else unsetLinkEditor(editor);
},
icon: LinkIcon,
}) as const;

export const TextColorItem = (editor: Editor): EditorMenuItem<"text-color"> => ({
key: "text-color",
name: "Color",
Expand Down Expand Up @@ -254,6 +270,7 @@ export const getEditorMenuItems = (editor: Editor | null): EditorMenuItem<TEdito
TableItem(editor),
ImageItem(editor),
HorizontalRuleItem(editor),
LinkItem(editor),
TextColorItem(editor),
BackgroundColorItem(editor),
TextAlignItem(editor),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { NodeSelection } from "@tiptap/pm/state";
import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from "react";
// plane imports
import { cn } from "@plane/utils";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
// helpers
import { getExtensionStorage } from "@/helpers/get-extension-storage";
// local imports
import { Pixel, TCustomImageAttributes, TCustomImageSize } from "../types";
import { ensurePixelString, getImageBlockId } from "../utils";
Expand Down Expand Up @@ -57,6 +61,8 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
const imageRef = useRef<HTMLImageElement>(null);
const [hasErroredOnFirstLoad, setHasErroredOnFirstLoad] = useState(false);
const [hasTriedRestoringImageOnce, setHasTriedRestoringImageOnce] = useState(false);
// extension options
const isTouchDevice = !!getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY).isTouchDevice;

const updateAttributesSafely = useCallback(
(attributes: Partial<TCustomImageAttributes>, errorMessage: string) => {
Expand Down Expand Up @@ -188,11 +194,15 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
const handleImageMouseDown = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
if (isTouchDevice) {
e.preventDefault();
editor.commands.blur();
}
const pos = getPos();
const nodeSelection = NodeSelection.create(editor.state.doc, pos);
editor.view.dispatch(editor.state.tr.setSelection(nodeSelection));
},
[editor, getPos]
[editor, getPos, isTouchDevice]
);

// show the image loader if the remote image's src or preview image from filesystem is not set yet (while loading the image post upload) (or)
Expand Down Expand Up @@ -254,7 +264,12 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
if (!resolvedImageSrc) {
throw new Error("No resolved image source available");
}
imageRef.current.src = resolvedImageSrc;
if (isTouchDevice) {
const refreshedSrc = await extension.options.getImageSource?.(imgNodeSrc);
imageRef.current.src = refreshedSrc;
} else {
imageRef.current.src = resolvedImageSrc;
}
} catch {
// if the image failed to even restore, then show the error state
setFailedToLoadImage(true);
Expand All @@ -281,14 +296,15 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
<ImageToolbarRoot
alignment={nodeAlignment ?? "left"}
editor={editor}
width={size.width}
height={size.height}
aspectRatio={size.aspectRatio === null ? 1 : size.aspectRatio}
src={resolvedImageSrc}
downloadSrc={resolvedDownloadSrc}
handleAlignmentChange={(alignment) =>
updateAttributesSafely({ alignment }, "Failed to update attributes while changing alignment:")
}
height={size.height}
isTouchDevice={isTouchDevice}
width={size.width}
src={resolvedImageSrc}
/>
)}
{selected && displayedImageSrc === resolvedImageSrc && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const CustomImageNodeView: React.FC<CustomImageNodeViewProps> = (props) =
const { editor, extension, node } = props;
const { src: imgNodeSrc } = node.attrs;

const [isUploaded, setIsUploaded] = useState(false);
const [isUploaded, setIsUploaded] = useState(!!imgNodeSrc);
const [resolvedSrc, setResolvedSrc] = useState<string | undefined>(undefined);
const [resolvedDownloadSrc, setResolvedDownloadSrc] = useState<string | undefined>(undefined);
const [imageFromFileSystem, setImageFromFileSystem] = useState<string | undefined>(undefined);
Expand All @@ -43,13 +43,13 @@ export const CustomImageNodeView: React.FC<CustomImageNodeViewProps> = (props) =
// the image is already uploaded if the image-component node has src attribute
// and we need to remove the blob from our file system
useEffect(() => {
if (resolvedSrc) {
if (resolvedSrc || imgNodeSrc) {
setIsUploaded(true);
setImageFromFileSystem(undefined);
} else {
setIsUploaded(false);
}
}, [resolvedSrc]);
}, [resolvedSrc, imgNodeSrc]);

useEffect(() => {
if (!imgNodeSrc) {
Expand Down
Loading
Loading