diff --git a/packages/editor/src/ce/extensions/document-extensions.tsx b/packages/editor/src/ce/extensions/document-extensions.tsx index 2809fcee4ef..e3c94fa0e10 100644 --- a/packages/editor/src/ce/extensions/document-extensions.tsx +++ b/packages/editor/src/ce/extensions/document-extensions.tsx @@ -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; }; diff --git a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx index a008d5c60ba..cd7d6f35489 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -14,6 +14,7 @@ import { EditorRefApi, ICollaborativeDocumentEditor } from "@/types"; const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { const { + onTransaction, aiHandler, containerClassName, disabledExtensions, @@ -43,6 +44,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { // use document editor const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeEditor({ + onTransaction, disabledExtensions, editorClassName, embedHandler, diff --git a/packages/editor/src/core/components/editors/editor-wrapper.tsx b/packages/editor/src/core/components/editors/editor-wrapper.tsx index 3e00dc2afdb..33f011535c5 100644 --- a/packages/editor/src/core/components/editors/editor-wrapper.tsx +++ b/packages/editor/src/core/components/editors/editor-wrapper.tsx @@ -28,6 +28,9 @@ export const EditorWrapper: React.FC = (props) => { forwardedRef, mentionHandler, onChange, + onTransaction, + handleEditorReady, + autofocus, placeholder, tabIndex, value, @@ -43,6 +46,9 @@ export const EditorWrapper: React.FC = (props) => { initialValue, mentionHandler, onChange, + onTransaction, + handleEditorReady, + autofocus, placeholder, tabIndex, value, diff --git a/packages/editor/src/core/components/editors/lite-text/editor.tsx b/packages/editor/src/core/components/editors/lite-text/editor.tsx index 924706aae87..849a3c3e234 100644 --- a/packages/editor/src/core/components/editors/lite-text/editor.tsx +++ b/packages/editor/src/core/components/editors/lite-text/editor.tsx @@ -1,4 +1,4 @@ -import { forwardRef } from "react"; +import { forwardRef, useMemo } from "react"; // components import { EditorWrapper } from "@/components/editors/editor-wrapper"; // extensions @@ -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 ; }; diff --git a/packages/editor/src/core/components/editors/rich-text/editor.tsx b/packages/editor/src/core/components/editors/rich-text/editor.tsx index 53f766ee21a..87dba8b4d11 100644 --- a/packages/editor/src/core/components/editors/rich-text/editor.tsx +++ b/packages/editor/src/core/components/editors/rich-text/editor.tsx @@ -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, - }) - ); + }), + ]; + if (!disabledExtensions?.includes("slash-commands")) { + extensions.push(SlashCommands()); + } return extensions; - }, [dragDropEnabled]); + }, [dragDropEnabled, disabledExtensions, externalExtensions]); return ( - {(editor) => <>{editor && }} + {(editor) => <>{editor && bubbleMenuEnabled && }} ); }; diff --git a/packages/editor/src/core/components/menus/menu-items.ts b/packages/editor/src/core/components/menus/menu-items.ts index 27f2124d2f6..0ece455ed25 100644 --- a/packages/editor/src/core/components/menus/menu-items.ts +++ b/packages/editor/src/core/components/menus/menu-items.ts @@ -20,10 +20,12 @@ import { Heading6, CaseSensitive, LucideIcon, + MinusSquare, Palette, } from "lucide-react"; // helpers import { + insertHorizontalRule, insertImage, insertTableCommand, setText, @@ -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", @@ -246,6 +257,7 @@ export const getEditorMenuItems = (editor: Editor | null): EditorMenuItem[] => { QuoteItem(editor), TableItem(editor), ImageItem(editor), + HorizontalRuleItem(editor), TextColorItem(editor), BackgroundColorItem(editor), ]; diff --git a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx index 65d9a38433a..f1a85ab1ba9 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx @@ -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"; @@ -37,7 +37,7 @@ const ensurePixelString = (value: Pixel | TDefault | number | undefin return value; }; -type CustomImageBlockProps = CustomImageNodeViewProps & { +type CustomImageBlockProps = CustoBaseImageNodeViewProps & { imageFromFileSystem: string; setFailedToLoadImage: (isError: boolean) => void; editorContainer: HTMLDivElement | null; @@ -56,10 +56,10 @@ export const CustomImageBlock: React.FC = (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({ width: ensurePixelString(nodeWidth, "35%"), @@ -210,13 +210,13 @@ export const CustomImageBlock: React.FC = (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 (
= (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); @@ -264,7 +264,7 @@ export const CustomImageBlock: React.FC = (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, @@ -277,14 +277,14 @@ export const CustomImageBlock: React.FC = (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 && (
)} {showImageResizer && ( diff --git a/packages/editor/src/core/extensions/custom-image/components/image-node.tsx b/packages/editor/src/core/extensions/custom-image/components/image-node.tsx index bdb8280c5b4..78caa87b301 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-node.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-node.tsx @@ -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) => 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(undefined); const [imageFromFileSystem, setImageFromFileSystem] = useState(undefined); const [failedToLoadImage, setFailedToLoadImage] = useState(false); @@ -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]); + + 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 ( @@ -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} diff --git a/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx b/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx index 36f1361ee80..8ad99bc4439 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx @@ -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; diff --git a/packages/editor/src/core/extensions/custom-image/custom-image.ts b/packages/editor/src/core/extensions/custom-image/custom-image.ts index effc5ee16c3..a232bb258a3 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -24,8 +24,8 @@ declare module "@tiptap/core" { imageComponent: { insertImageComponent: ({ file, pos, event }: InsertImageComponentProps) => ReturnType; uploadImage: (file: File) => () => Promise | undefined; + getImageSource?: (path: string) => () => Promise; restoreImage: (src: string) => () => Promise; - getImageSource?: (path: string) => () => string; }; } } @@ -193,10 +193,10 @@ export const CustomImageExtension = (props: TFileHandler) => { const fileUrl = await upload(file); return fileUrl; }, + getImageSource: (path: string) => async () => await getAssetSrc(path), restoreImage: (src: string) => async () => { await restoreImageFn(src); }, - getImageSource: (path: string) => () => getAssetSrc(path), }; }, diff --git a/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts b/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts index 07d890cd688..3248329f033 100644 --- a/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts @@ -68,7 +68,7 @@ export const CustomReadOnlyImageExtension = (props: Pick () => getAssetSrc(path), + getImageSource: (path: string) => async () => await getAssetSrc(path), }; }, diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index f7666bfe24b..f549719f26e 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -76,7 +76,7 @@ export const ImageExtension = (fileHandler: TFileHandler) => { addCommands() { return { - getImageSource: (path: string) => () => getAssetSrc(path), + getImageSource: (path: string) => async () => await getAssetSrc(path), }; }, diff --git a/packages/editor/src/core/extensions/image/read-only-image.tsx b/packages/editor/src/core/extensions/image/read-only-image.tsx index c884a43ee72..ce1581a8eb3 100644 --- a/packages/editor/src/core/extensions/image/read-only-image.tsx +++ b/packages/editor/src/core/extensions/image/read-only-image.tsx @@ -26,7 +26,7 @@ export const ReadOnlyImageExtension = (props: Pick) addCommands() { return { - getImageSource: (path: string) => () => getAssetSrc(path), + getImageSource: (path: string) => async () => await getAssetSrc(path), }; }, diff --git a/packages/editor/src/core/extensions/mentions/mention-node-view.tsx b/packages/editor/src/core/extensions/mentions/mention-node-view.tsx index ca2f39b8ce5..5121eae4aff 100644 --- a/packages/editor/src/core/extensions/mentions/mention-node-view.tsx +++ b/packages/editor/src/core/extensions/mentions/mention-node-view.tsx @@ -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(); }, [props.extension.options]); + const handleClick = (event: React.MouseEvent) => { + if (!props.node.attrs.redirect_uri) { + event.preventDefault(); + } + }; + return ( { + 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(); diff --git a/packages/editor/src/core/helpers/scroll-to-node.ts b/packages/editor/src/core/helpers/scroll-to-node.ts index 65d32a7d214..973f3cf141b 100644 --- a/packages/editor/src/core/helpers/scroll-to-node.ts +++ b/packages/editor/src/core/helpers/scroll-to-node.ts @@ -32,6 +32,26 @@ function scrollToNode(editor: Editor, pos: number): void { } } +export function scrollToNodeViaDOMCoordinates(editor: Editor, pos: number, behavior?: ScrollBehavior): void { + const view = editor.view; + + // Get the coordinates of the position + const coords = view.coordsAtPos(pos); + + if (coords) { + // Scroll to the coordinates + window.scrollTo({ + top: coords.top + window.scrollY - window.innerHeight / 2, + behavior: behavior, + }); + + // Optionally, you can also focus the editor + view.focus(); + } else { + console.warn("Unable to find coordinates for the given position"); + } +} + export function scrollSummary(editor: Editor, marking: IMarking) { if (editor) { const pos = findNthH1(editor, marking.sequence, marking.level); diff --git a/packages/editor/src/core/hooks/use-collaborative-editor.ts b/packages/editor/src/core/hooks/use-collaborative-editor.ts index 5a004bff284..5be0ca529cd 100644 --- a/packages/editor/src/core/hooks/use-collaborative-editor.ts +++ b/packages/editor/src/core/hooks/use-collaborative-editor.ts @@ -13,6 +13,7 @@ import { TCollaborativeEditorProps } from "@/types"; export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { const { + onTransaction, disabledExtensions, editorClassName, editorProps = {}, @@ -75,6 +76,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { const editor = useEditor({ id, + onTransaction, editorProps, editorClassName, enableHistory: false, diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index beee9c929d0..67d7799660f 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -12,7 +12,7 @@ import { CoreEditorExtensions } from "@/extensions"; // helpers import { getParagraphCount } from "@/helpers/common"; import { insertContentAtSavedSelection } from "@/helpers/insert-content-at-cursor-position"; -import { IMarking, scrollSummary } from "@/helpers/scroll-to-node"; +import { IMarking, scrollSummary, scrollToNodeViaDOMCoordinates } from "@/helpers/scroll-to-node"; // props import { CoreEditorProps } from "@/props"; // types @@ -33,6 +33,8 @@ export interface CustomEditorProps { suggestions?: () => Promise; }; onChange?: (json: object, html: string) => void; + onTransaction?: () => void; + autofocus?: boolean; placeholder?: string | ((isFocused: boolean, value: string) => string); provider?: HocuspocusProvider; tabIndex?: number; @@ -54,10 +56,12 @@ export const useEditor = (props: CustomEditorProps) => { initialValue, mentionHandler, onChange, + onTransaction, placeholder, provider, tabIndex, value, + autofocus = false, } = props; // states @@ -66,6 +70,7 @@ export const useEditor = (props: CustomEditorProps) => { const editorRef: MutableRefObject = useRef(null); const savedSelectionRef = useRef(savedSelection); const editor = useTiptapEditor({ + autofocus, editorProps: { ...CoreEditorProps({ editorClassName, @@ -87,7 +92,10 @@ export const useEditor = (props: CustomEditorProps) => { ], content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "

", onCreate: () => handleEditorReady?.(true), - onTransaction: ({ editor }) => setSavedSelection(editor.state.selection), + onTransaction: ({ editor }) => { + setSavedSelection(editor.state.selection); + onTransaction?.(); + }, onUpdate: ({ editor }) => onChange?.(editor.getJSON(), editor.getHTML()), onDestroy: () => handleEditorReady?.(false), }); @@ -120,6 +128,13 @@ export const useEditor = (props: CustomEditorProps) => { useImperativeHandle( forwardedRef, () => ({ + blur: () => editorRef.current?.commands.blur(), + scrollToNodeViaDOMCoordinates(behavior?: ScrollBehavior, pos?: number) { + const resolvedPos = pos ?? savedSelection?.from; + if (!editorRef.current || !resolvedPos) return; + scrollToNodeViaDOMCoordinates(editorRef.current, resolvedPos, behavior); + }, + getCurrentCursorPosition: () => savedSelection?.from, clearEditor: (emitUpdate = false) => { editorRef.current?.chain().setMeta("skipImageDeletion", true).clearContent(emitUpdate).run(); }, diff --git a/packages/editor/src/core/types/collaboration.ts b/packages/editor/src/core/types/collaboration.ts index 60721a5a662..8609995ed83 100644 --- a/packages/editor/src/core/types/collaboration.ts +++ b/packages/editor/src/core/types/collaboration.ts @@ -36,6 +36,7 @@ type TCollaborativeEditorHookProps = { }; export type TCollaborativeEditorProps = TCollaborativeEditorHookProps & { + onTransaction?: () => void; embedHandler?: TEmbedConfig; fileHandler: TFileHandler; forwardedRef?: React.MutableRefObject; diff --git a/packages/editor/src/core/types/config.ts b/packages/editor/src/core/types/config.ts index 67043ef9a14..3bb4d1af292 100644 --- a/packages/editor/src/core/types/config.ts +++ b/packages/editor/src/core/types/config.ts @@ -1,7 +1,7 @@ import { DeleteImage, RestoreImage, UploadImage } from "@/types"; export type TFileHandler = { - getAssetSrc: (path: string) => string; + getAssetSrc: (path: string) => Promise; cancel: () => void; delete: DeleteImage; upload: UploadImage; diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 1aa85822451..1eeb2b191e9 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -35,6 +35,9 @@ export type EditorReadOnlyRefApi = { }; export interface EditorRefApi extends EditorReadOnlyRefApi { + blur: () => void; + scrollToNodeViaDOMCoordinates: (behavior?: ScrollBehavior, position?: number) => void; + getCurrentCursorPosition: () => number | undefined; setEditorValueAtCursorPosition: (content: string) => void; executeMenuItemCommand: ( props: @@ -68,6 +71,7 @@ export interface EditorRefApi extends EditorReadOnlyRefApi { export interface IEditorProps { containerClassName?: string; displayConfig?: TDisplayConfig; + disabledExtensions?: TExtensions[]; editorClassName?: string; fileHandler: TFileHandler; forwardedRef?: React.MutableRefObject; @@ -78,22 +82,26 @@ export interface IEditorProps { suggestions?: () => Promise; }; onChange?: (json: object, html: string) => void; + onTransaction?: () => void; + handleEditorReady?: (value: boolean) => void; + autofocus?: boolean; onEnterKeyPress?: (e?: any) => void; placeholder?: string | ((isFocused: boolean, value: string) => string); tabIndex?: number; - value?: string | null; + value?: string | null; +} +export interface ILiteTextEditor extends IEditorProps { + extensions?: any[]; } - -export type ILiteTextEditor = IEditorProps; - export interface IRichTextEditor extends IEditorProps { + extensions?: any[]; + bubbleMenuEnabled?: boolean; dragDropEnabled?: boolean; } export interface ICollaborativeDocumentEditor extends Omit { aiHandler?: TAIHandler; - disabledExtensions: TExtensions[]; embedHandler: TEmbedConfig; handleEditorReady?: (value: boolean) => void; id: string; diff --git a/packages/editor/src/core/types/extensions.ts b/packages/editor/src/core/types/extensions.ts index da8713f10cd..2be17a4effa 100644 --- a/packages/editor/src/core/types/extensions.ts +++ b/packages/editor/src/core/types/extensions.ts @@ -1 +1 @@ -export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed"; +export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed" | "slash-commands"| "enter-key"; diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 292dc53fb2c..ed7d9134698 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -1,10 +1,10 @@ // styles // import "./styles/tailwind.css"; -import "src/styles/variables.css"; -import "src/styles/editor.css"; -import "src/styles/table.css"; -import "src/styles/github-dark.css"; -import "src/styles/drag-drop.css"; +import "./styles/variables.css"; +import "./styles/editor.css"; +import "./styles/table.css"; +import "./styles/github-dark.css"; +import "./styles/drag-drop.css"; // editors export { diff --git a/packages/editor/src/styles/editor.css b/packages/editor/src/styles/editor.css index 05c9dc1456b..8ee5f860543 100644 --- a/packages/editor/src/styles/editor.css +++ b/packages/editor/src/styles/editor.css @@ -1,3 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + .ProseMirror { position: relative; word-wrap: break-word; diff --git a/space/helpers/editor.helper.ts b/space/helpers/editor.helper.ts index 648e409e70f..b037055afeb 100644 --- a/space/helpers/editor.helper.ts +++ b/space/helpers/editor.helper.ts @@ -31,7 +31,7 @@ export const getEditorFileHandlers = (args: TArgs): TFileHandler => { const { anchor, uploadFile, workspaceId } = args; return { - getAssetSrc: (path) => { + getAssetSrc: async (path) => { if (!path) return ""; if (path?.startsWith("http")) { return path; @@ -70,7 +70,7 @@ export const getReadOnlyEditorFileHandlers = ( const { anchor } = args; return { - getAssetSrc: (path) => { + getAssetSrc: async (path) => { if (!path) return ""; if (path?.startsWith("http")) { return path; diff --git a/web/helpers/editor.helper.ts b/web/helpers/editor.helper.ts index 801cb899641..f6c9034214f 100644 --- a/web/helpers/editor.helper.ts +++ b/web/helpers/editor.helper.ts @@ -43,7 +43,7 @@ export const getEditorFileHandlers = (args: TArgs): TFileHandler => { const { maxFileSize, projectId, uploadFile, workspaceId, workspaceSlug } = args; return { - getAssetSrc: (path) => { + getAssetSrc: async (path) => { if (!path) return ""; if (path?.startsWith("http")) { return path; @@ -94,7 +94,7 @@ export const getReadOnlyEditorFileHandlers = ( const { projectId, workspaceSlug } = args; return { - getAssetSrc: (path) => { + getAssetSrc: async (path) => { if (!path) return ""; if (path?.startsWith("http")) { return path;