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
3 changes: 2 additions & 1 deletion packages/editor/src/ce/extensions/document-extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ type Props = {
};

export const DocumentEditorAdditionalExtensions = (_props: Props) => {
const extensions: Extensions = [SlashCommands()];
const { disabledExtensions } = _props;
const extensions: Extensions = disabledExtensions?.includes("slash-commands") ? [] : [SlashCommands()];

return extensions;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { EditorRefApi, ICollaborativeDocumentEditor } from "@/types";

const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
const {
onTransaction,
aiHandler,
containerClassName,
disabledExtensions,
Expand Down Expand Up @@ -43,6 +44,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {

// use document editor
const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeEditor({
onTransaction,
disabledExtensions,
editorClassName,
embedHandler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const EditorWrapper: React.FC<Props> = (props) => {
forwardedRef,
mentionHandler,
onChange,
onTransaction,
handleEditorReady,
autofocus,
placeholder,
tabIndex,
value,
Expand All @@ -43,6 +46,9 @@ export const EditorWrapper: React.FC<Props> = (props) => {
initialValue,
mentionHandler,
onChange,
onTransaction,
handleEditorReady,
autofocus,
placeholder,
tabIndex,
value,
Expand Down
12 changes: 9 additions & 3 deletions packages/editor/src/core/components/editors/lite-text/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef } from "react";
import { forwardRef, useMemo } from "react";
// components
import { EditorWrapper } from "@/components/editors/editor-wrapper";
// extensions
Expand All @@ -7,9 +7,15 @@ import { EnterKeyExtension } from "@/extensions";
import { EditorRefApi, ILiteTextEditor } from "@/types";

const LiteTextEditor = (props: ILiteTextEditor) => {
const { onEnterKeyPress } = props;
const { onEnterKeyPress, disabledExtensions, extensions: externalExtensions = [] } = props;

const extensions = [EnterKeyExtension(onEnterKeyPress)];
const extensions = useMemo(
() => [
...externalExtensions,
...(disabledExtensions?.includes("enter-key") ? [] : [EnterKeyExtension(onEnterKeyPress)]),
],
[externalExtensions, disabledExtensions, onEnterKeyPress]
);

return <EditorWrapper {...props} extensions={extensions} />;
};
Expand Down
23 changes: 15 additions & 8 deletions packages/editor/src/core/components/editors/rich-text/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,31 @@ import { SideMenuExtension, SlashCommands } from "@/extensions";
import { EditorRefApi, IRichTextEditor } from "@/types";

const RichTextEditor = (props: IRichTextEditor) => {
const { dragDropEnabled } = props;
const {
disabledExtensions,
dragDropEnabled,
bubbleMenuEnabled = true,
extensions: externalExtensions = [],
} = props;

const getExtensions = useCallback(() => {
const extensions = [SlashCommands()];

extensions.push(
const extensions = [
...externalExtensions,
SideMenuExtension({
aiEnabled: false,
dragDropEnabled: !!dragDropEnabled,
})
);
}),
Comment on lines +19 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Hardcoding aiEnabled: false is incorrect and should be configurable

The codebase shows that AI functionality is meant to be configurable through the disabledExtensions prop. In use-collaborative-editor.ts, the aiEnabled flag is dynamically set based on whether "ai" is included in disabledExtensions. Hardcoding it to false in the rich-text editor prevents this configuration from working as intended.

  • packages/editor/src/core/hooks/use-collaborative-editor.ts: Uses aiEnabled: !disabledExtensions?.includes("ai")
  • packages/editor/src/core/components/editors/rich-text/editor.tsx: Incorrectly hardcodes aiEnabled: false
🔗 Analysis chain

Verify aiEnabled flag usage

The aiEnabled: false is hardcoded in SideMenuExtension. This might conflict with AI-related features mentioned in other components.

Let's check for AI-related configurations:


Let me check the implementation details of the AI feature to understand if hardcoding aiEnabled: false is correct or not.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for AI-related configurations and usages
rg -g '!*.{json,lock}' 'aiEnabled' packages/editor/

Length of output: 504


Script:

#!/bin/bash
# Check the implementation of use-collaborative-editor.ts
cat packages/editor/src/core/hooks/use-collaborative-editor.ts

# Check how the AI feature is used in side-menu.tsx
cat packages/editor/src/core/extensions/side-menu.tsx

Length of output: 8214

];
if (!disabledExtensions?.includes("slash-commands")) {
extensions.push(SlashCommands());
}

return extensions;
}, [dragDropEnabled]);
}, [dragDropEnabled, disabledExtensions, externalExtensions]);

return (
<EditorWrapper {...props} extensions={getExtensions()}>
{(editor) => <>{editor && <EditorBubbleMenu editor={editor} />}</>}
{(editor) => <>{editor && bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}</>}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify conditional rendering

The nested ternary with multiple conditions can be simplified.

Consider this more readable alternative:

-{(editor) => <>{editor && bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}</>}
+{(editor) => (
+  bubbleMenuEnabled && editor ? <EditorBubbleMenu editor={editor} /> : null
+)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{(editor) => <>{editor && bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}</>}
{(editor) => (
bubbleMenuEnabled && editor ? <EditorBubbleMenu editor={editor} /> : null
)}

</EditorWrapper>
Comment on lines 34 to 36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Based on the gathered information, I can now provide a final response:

Type safety concerns identified in EditorBubbleMenu

The component has a potential type safety issue:

  • EditorBubbleMenuProps is correctly typed as Omit<BubbleMenuProps, "children">
  • However, the component implementation uses props: any which bypasses type checking
  • The editor prop from TipTap's BubbleMenu is being passed through without proper typing
  • Consider removing the : any type annotation in EditorBubbleMenu component to leverage proper type inference
  • Update the component to properly type the props parameter: export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
🔗 Analysis chain

Verify editor prop type safety

While the conditional rendering works, let's ensure the editor prop type is correctly handled.


Let me gather more information about the type definitions and usage to ensure type safety.


Let me check one more thing to understand how the editor prop is typed in the @tiptap/react package.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check EditorBubbleMenu component's prop types
rg -A 5 'EditorBubbleMenu.*Props|interface.*EditorBubbleMenu' packages/editor/

Length of output: 1568


Script:

#!/bin/bash
# Check BubbleMenuProps type definition and editor prop usage
rg -A 5 'BubbleMenuProps|type.*BubbleMenu' packages/editor/

# Check EditorWrapper component's type definitions
ast-grep --pattern 'interface EditorWrapperProps {
  $$$
}'

Length of output: 2158


Script:

#!/bin/bash
# Check EditorWrapper implementation and type usage
ast-grep --pattern 'const EditorWrapper = ({ $$$' 

# Check if there are any type errors related to editor prop
rg -B2 -A2 'editor: ' packages/editor/src/core/components/editors/rich-text/

Length of output: 130

);
};
Expand Down
12 changes: 12 additions & 0 deletions packages/editor/src/core/components/menus/menu-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import {
Heading6,
CaseSensitive,
LucideIcon,
MinusSquare,
Palette,
} from "lucide-react";
// helpers
import {
insertHorizontalRule,
insertImage,
insertTableCommand,
setText,
Expand Down Expand Up @@ -208,6 +210,15 @@ export const ImageItem = (editor: Editor) =>
icon: ImageIcon,
}) as const;

export const HorizontalRuleItem = (editor: Editor) =>
({
key: "divider",
name: "Divider",
isActive: () => editor?.isActive("horizontalRule"),
command: () => insertHorizontalRule(editor),
icon: MinusSquare,
}) as const;

export const TextColorItem = (editor: Editor): EditorMenuItem => ({
key: "text-color",
name: "Color",
Expand Down Expand Up @@ -246,6 +257,7 @@ export const getEditorMenuItems = (editor: Editor | null): EditorMenuItem[] => {
QuoteItem(editor),
TableItem(editor),
ImageItem(editor),
HorizontalRuleItem(editor),
TextColorItem(editor),
BackgroundColorItem(editor),
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from "react";
import { NodeSelection } from "@tiptap/pm/state";
// extensions
import { CustomImageNodeViewProps, ImageToolbarRoot } from "@/extensions/custom-image";
import { CustoBaseImageNodeViewProps, ImageToolbarRoot } from "@/extensions/custom-image";
// helpers
import { cn } from "@/helpers/common";

Expand Down Expand Up @@ -37,7 +37,7 @@ const ensurePixelString = <TDefault,>(value: Pixel | TDefault | number | undefin
return value;
};

type CustomImageBlockProps = CustomImageNodeViewProps & {
type CustomImageBlockProps = CustoBaseImageNodeViewProps & {
imageFromFileSystem: string;
setFailedToLoadImage: (isError: boolean) => void;
editorContainer: HTMLDivElement | null;
Expand All @@ -56,10 +56,10 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
getPos,
editor,
editorContainer,
src: remoteImageSrc,
src: resolvedImageSrc,
setEditorContainer,
} = props;
const { width: nodeWidth, height: nodeHeight, aspectRatio: nodeAspectRatio } = node.attrs;
const { width: nodeWidth, height: nodeHeight, aspectRatio: nodeAspectRatio, src: imgNodeSrc } = node.attrs;
// states
const [size, setSize] = useState<Size>({
width: ensurePixelString(nodeWidth, "35%"),
Expand Down Expand Up @@ -210,13 +210,13 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {

// 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)
// if the initial resize (from 35% width and "auto" height attrs to the actual size in px) is not complete
const showImageLoader = !(remoteImageSrc || imageFromFileSystem) || !initialResizeComplete || hasErroredOnFirstLoad;
const showImageLoader = !(resolvedImageSrc || imageFromFileSystem) || !initialResizeComplete || hasErroredOnFirstLoad;
// show the image utils only if the remote image's (post upload) src is set and the initial resize is complete (but not while we're showing the preview imageFromFileSystem)
const showImageUtils = remoteImageSrc && initialResizeComplete;
const showImageUtils = resolvedImageSrc && initialResizeComplete;
// show the image resizer only if the editor is editable, the remote image's (post upload) src is set and the initial resize is complete (but not while we're showing the preview imageFromFileSystem)
const showImageResizer = editor.isEditable && remoteImageSrc && initialResizeComplete;
const showImageResizer = editor.isEditable && resolvedImageSrc && initialResizeComplete;
// show the preview image from the file system if the remote image's src is not set
const displayedImageSrc = remoteImageSrc || imageFromFileSystem;
const displayedImageSrc = resolvedImageSrc || imageFromFileSystem;

return (
<div
Expand Down Expand Up @@ -248,8 +248,8 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
try {
setHasErroredOnFirstLoad(true);
// this is a type error from tiptap, don't remove await until it's fixed
await editor?.commands.restoreImage?.(node.attrs.src);
imageRef.current.src = remoteImageSrc;
await editor?.commands.restoreImage?.(imgNodeSrc);
imageRef.current.src = resolvedImageSrc;
} catch {
// if the image failed to even restore, then show the error state
setFailedToLoadImage(true);
Expand All @@ -264,7 +264,7 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
// hide the image while the background calculations of the image loader are in progress (to avoid flickering) and show the loader until then
hidden: showImageLoader,
"read-only-image": !editor.isEditable,
"blur-sm opacity-80 loading-image": !remoteImageSrc,
"blur-sm opacity-80 loading-image": !resolvedImageSrc,
})}
style={{
width: size.width,
Expand All @@ -277,14 +277,14 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
"absolute top-1 right-1 z-20 bg-black/40 rounded opacity-0 pointer-events-none group-hover/image-component:opacity-100 group-hover/image-component:pointer-events-auto transition-opacity"
}
image={{
src: remoteImageSrc,
src: resolvedImageSrc,
aspectRatio: size.aspectRatio,
height: size.height,
width: size.width,
}}
/>
)}
{selected && displayedImageSrc === remoteImageSrc && (
{selected && displayedImageSrc === resolvedImageSrc && (
<div className="absolute inset-0 size-full bg-custom-primary-500/30" />
)}
{showImageResizer && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@ import { Editor, NodeViewProps, NodeViewWrapper } from "@tiptap/react";
// extensions
import { CustomImageBlock, CustomImageUploader, ImageAttributes } from "@/extensions/custom-image";

export type CustomImageComponentProps = {
export type CustoBaseImageNodeViewProps = {
getPos: () => number;
editor: Editor;
node: NodeViewProps["node"] & {
attrs: ImageAttributes;
};
updateAttributes: (attrs: ImageAttributes) => void;
updateAttributes: (attrs: Partial<ImageAttributes>) => void;
selected: boolean;
};

export type CustomImageNodeViewProps = NodeViewProps & CustomImageComponentProps;
export type CustomImageNodeProps = NodeViewProps & CustoBaseImageNodeViewProps;

export const CustomImageNode = (props: CustomImageNodeViewProps) => {
export const CustomImageNode = (props: CustomImageNodeProps) => {
const { getPos, editor, node, updateAttributes, selected } = props;
const { src: remoteImageSrc } = node.attrs;
const { src: imgNodeSrc } = node.attrs;

const [isUploaded, setIsUploaded] = useState(false);
const [resolvedSrc, setResolvedSrc] = useState<string | undefined>(undefined);
const [imageFromFileSystem, setImageFromFileSystem] = useState<string | undefined>(undefined);
const [failedToLoadImage, setFailedToLoadImage] = useState(false);

Expand All @@ -39,13 +40,22 @@ export const CustomImageNode = (props: CustomImageNodeViewProps) => {
// 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 (remoteImageSrc) {
if (resolvedSrc) {
setIsUploaded(true);
setImageFromFileSystem(undefined);
} else {
setIsUploaded(false);
}
}, [remoteImageSrc]);
}, [resolvedSrc]);
Comment on lines +43 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider simplifying the upload status logic

The upload status logic can be more concise by directly using the boolean expression.

-if (resolvedSrc) {
-  setIsUploaded(true);
-  setImageFromFileSystem(undefined);
-} else {
-  setIsUploaded(false);
-}
+setIsUploaded(Boolean(resolvedSrc));
+if (resolvedSrc) setImageFromFileSystem(undefined);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (resolvedSrc) {
setIsUploaded(true);
setImageFromFileSystem(undefined);
} else {
setIsUploaded(false);
}
}, [remoteImageSrc]);
}, [resolvedSrc]);
setIsUploaded(Boolean(resolvedSrc));
if (resolvedSrc) setImageFromFileSystem(undefined);
}, [resolvedSrc]);


useEffect(() => {
const getImageSource = async () => {
// @ts-expect-error function not expected here, but will still work and don't remove await
const url: string = await editor?.commands?.getImageSource?.(imgNodeSrc);
setResolvedSrc(url as string);
};
getImageSource();
}, [imageFromFileSystem, node.attrs.src]);

return (
<NodeViewWrapper>
Expand All @@ -55,8 +65,7 @@ export const CustomImageNode = (props: CustomImageNodeViewProps) => {
imageFromFileSystem={imageFromFileSystem}
editorContainer={editorContainer}
editor={editor}
// @ts-expect-error function not expected here, but will still work
src={editor?.commands?.getImageSource?.(remoteImageSrc)}
src={resolvedSrc}
getPos={getPos}
node={node}
setEditorContainer={setEditorContainer}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { cn } from "@/helpers/common";
// hooks
import { useUploader, useDropZone, uploadFirstImageAndInsertRemaining } from "@/hooks/use-file-upload";
// extensions
import { type CustomImageComponentProps, getImageComponentImageFileMap } from "@/extensions/custom-image";
import { CustoBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image";

type CustomImageUploaderProps = CustomImageComponentProps & {
type CustomImageUploaderProps = CustoBaseImageNodeViewProps & {
maxFileSize: number;
loadImageFromFileSystem: (file: string) => void;
failedToLoadImage: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ declare module "@tiptap/core" {
imageComponent: {
insertImageComponent: ({ file, pos, event }: InsertImageComponentProps) => ReturnType;
uploadImage: (file: File) => () => Promise<string> | undefined;
getImageSource?: (path: string) => () => Promise<string>;
restoreImage: (src: string) => () => Promise<void>;
getImageSource?: (path: string) => () => string;
};
}
}
Expand Down Expand Up @@ -193,10 +193,10 @@ export const CustomImageExtension = (props: TFileHandler) => {
const fileUrl = await upload(file);
return fileUrl;
},
getImageSource: (path: string) => async () => await getAssetSrc(path),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for getAssetSrc failures.

While the async implementation is good for mobile performance, consider adding error handling to gracefully handle cases where getAssetSrc fails.

Consider updating the implementation:

-        getImageSource: (path: string) => async () => await getAssetSrc(path),
+        getImageSource: (path: string) => async () => {
+          try {
+            return await getAssetSrc(path);
+          } catch (error) {
+            console.error('Failed to get asset source:', error);
+            throw error;
+          }
+        },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getImageSource: (path: string) => async () => await getAssetSrc(path),
getImageSource: (path: string) => async () => {
try {
return await getAssetSrc(path);
} catch (error) {
console.error('Failed to get asset source:', error);
throw error;
}
},

restoreImage: (src: string) => async () => {
await restoreImageFn(src);
},
getImageSource: (path: string) => () => getAssetSrc(path),
};
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const CustomReadOnlyImageExtension = (props: Pick<TFileHandler, "getAsset

addCommands() {
return {
getImageSource: (path: string) => () => getAssetSrc(path),
getImageSource: (path: string) => async () => await getAssetSrc(path),
};
},

Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/core/extensions/image/extension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const ImageExtension = (fileHandler: TFileHandler) => {

addCommands() {
return {
getImageSource: (path: string) => () => getAssetSrc(path),
getImageSource: (path: string) => async () => await getAssetSrc(path),
};
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const ReadOnlyImageExtension = (props: Pick<TFileHandler, "getAssetSrc">)

addCommands() {
return {
getImageSource: (path: string) => () => getAssetSrc(path),
getImageSource: (path: string) => async () => await getAssetSrc(path),
};
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ export const MentionNodeView = (props) => {
useEffect(() => {
if (!props.extension.options.mentionHighlights) return;
const hightlights = async () => {
const userId = await props.extension.options.mentionHighlights();
const userId = await props.extension.options.mentionHighlights?.();
setHighlightsState(userId);
};
hightlights();
Comment on lines +21 to 24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and variable naming in mentionHighlights.

The current implementation has several issues:

  1. No error handling for the async call
  2. Misleading variable name (userId stores highlights)
  3. No loading or error state management

Consider this improvement:

- const userId = await props.extension.options.mentionHighlights?.();
- setHighlightsState(userId);
+ try {
+   const highlights = await props.extension.options.mentionHighlights?.();
+   setHighlightsState(highlights);
+ } catch (error) {
+   console.error('Failed to fetch mention highlights:', error);
+   setHighlightsState([]);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const userId = await props.extension.options.mentionHighlights?.();
setHighlightsState(userId);
};
hightlights();
try {
const highlights = await props.extension.options.mentionHighlights?.();
setHighlightsState(highlights);
} catch (error) {
console.error('Failed to fetch mention highlights:', error);
setHighlightsState([]);
}
};
hightlights();

}, [props.extension.options]);

const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (!props.node.attrs.redirect_uri) {
event.preventDefault();
}
};

return (
<NodeViewWrapper className="mention-component inline w-fit">
<a
href={props.node.attrs.redirect_uri}
href={props.node.attrs.redirect_uri || "#"}
target="_blank"
className={cn("mention rounded bg-custom-primary-100/20 px-1 py-0.5 font-medium text-custom-primary-100", {
"bg-yellow-500/20 text-yellow-500": highlightsState
Expand Down
4 changes: 4 additions & 0 deletions packages/editor/src/core/helpers/editor-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export const toggleBackgroundColor = (color: string | undefined, editor: Editor,
}
};

export const insertHorizontalRule = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setHorizontalRule().run();
else editor.chain().focus().setHorizontalRule().run();
}
export const insertCallout = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).insertCallout().run();
else editor.chain().focus().insertCallout().run();
Expand Down
Loading