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 2bd84fcb310..de2a20ab965 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 @@ -76,7 +76,7 @@ export const CustomImageNode = (props: CustomImageNodeProps) => { failedToLoadImage={failedToLoadImage} getPos={getPos} loadImageFromFileSystem={setImageFromFileSystem} - maxFileSize={editor.storage.imageComponent.maxFileSize} + maxFileSize={editor.storage.imageComponent?.maxFileSize} node={node} setIsUploaded={setIsUploaded} selected={selected} diff --git a/packages/editor/src/core/extensions/custom-image/components/upload-status.tsx b/packages/editor/src/core/extensions/custom-image/components/upload-status.tsx index 7b23713e9e1..8b71713d24a 100644 --- a/packages/editor/src/core/extensions/custom-image/components/upload-status.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/upload-status.tsx @@ -16,7 +16,7 @@ export const ImageUploadStatus: React.FC = (props) => { // subscribe to image upload status const uploadStatus: number | undefined = useEditorState({ editor, - selector: ({ editor }) => editor.storage.imageComponent.assetsUploadStatus[nodeId], + selector: ({ editor }) => editor.storage.imageComponent?.assetsUploadStatus[nodeId], }); useEffect(() => { 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 a4d38ceb59b..3e35fd0e38c 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -22,7 +22,7 @@ declare module "@tiptap/core" { imageComponent: { insertImageComponent: ({ file, pos, event }: InsertImageComponentProps) => ReturnType; uploadImage: (blockId: string, file: File) => () => Promise | undefined; - updateAssetsUploadStatus: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void; + updateAssetsUploadStatus?: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void; getImageSource?: (path: string) => () => Promise; restoreImage: (src: string) => () => Promise; }; diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 485269e8289..8b9290d62d3 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -50,8 +50,7 @@ type TArguments = { export const CoreEditorExtensions = (args: TArguments): Extensions => { const { disabledExtensions, enableHistory, fileHandler, mentionHandler, placeholder, tabIndex } = args; - return [ - // @ts-expect-error tiptap types are incorrect + const extensions = [ StarterKit.configure({ bulletList: { HTMLAttributes: { @@ -109,12 +108,6 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { }, }), CustomTypographyExtension, - ImageExtension(fileHandler).configure({ - HTMLAttributes: { - class: "rounded-md", - }, - }), - CustomImageExtension(fileHandler), TiptapUnderline, TextStyle, TaskList.configure({ @@ -152,7 +145,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { if (node.type.name === "heading") return `Heading ${node.attrs.level}`; - if (editor.storage.imageComponent.uploadInProgress) return ""; + if (editor.storage.imageComponent?.uploadInProgress) return ""; const shouldHidePlaceholder = editor.isActive("table") || @@ -179,4 +172,18 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { disabledExtensions, }), ]; + + if (!disabledExtensions.includes("image")) { + extensions.push( + ImageExtension(fileHandler).configure({ + HTMLAttributes: { + class: "rounded-md", + }, + }), + CustomImageExtension(fileHandler) + ); + } + + // @ts-expect-error tiptap types are incorrect + return extensions; }; diff --git a/packages/editor/src/core/extensions/image/image-component-without-props.tsx b/packages/editor/src/core/extensions/image/image-component-without-props.tsx index b6dbd4c308d..7167e622c32 100644 --- a/packages/editor/src/core/extensions/image/image-component-without-props.tsx +++ b/packages/editor/src/core/extensions/image/image-component-without-props.tsx @@ -48,6 +48,7 @@ export const CustomImageComponentWithoutProps = () => return { fileMap: new Map(), deletedImageSet: new Map(), + assetsUploadStatus: {}, }; }, }); diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.tsx index eb525665721..b949fe6b7a1 100644 --- a/packages/editor/src/core/extensions/read-only-extensions.tsx +++ b/packages/editor/src/core/extensions/read-only-extensions.tsx @@ -41,8 +41,7 @@ type Props = { export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { const { disabledExtensions, fileHandler, mentionHandler } = props; - return [ - // @ts-expect-error tiptap types are incorrect + const extensions = [ StarterKit.configure({ bulletList: { HTMLAttributes: { @@ -94,12 +93,6 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { }, }), CustomTypographyExtension, - ReadOnlyImageExtension(fileHandler).configure({ - HTMLAttributes: { - class: "rounded-md", - }, - }), - CustomReadOnlyImageExtension(fileHandler), TiptapUnderline, TextStyle, TaskList.configure({ @@ -136,4 +129,18 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { disabledExtensions, }), ]; + + if (!disabledExtensions.includes("image")) { + extensions.push( + ReadOnlyImageExtension(fileHandler).configure({ + HTMLAttributes: { + class: "rounded-md", + }, + }), + CustomReadOnlyImageExtension(fileHandler) + ); + } + + // @ts-expect-error tiptap types are incorrect + return extensions; }; diff --git a/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx b/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx index 034d3d89786..9fcc733aef6 100644 --- a/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx +++ b/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx @@ -43,7 +43,7 @@ import { CommandProps, ISlashCommandItem, TSlashCommandSectionKeys } from "@/typ // plane editor extensions import { coreEditorAdditionalSlashCommandOptions } from "@/plane-editor/extensions"; // local types -import { TExtensionProps } from "./root"; +import { TExtensionProps, TSlashCommandAdditionalOption } from "./root"; export type TSlashCommandSection = { key: TSlashCommandSectionKeys; @@ -54,7 +54,7 @@ export type TSlashCommandSection = { export const getSlashCommandFilteredSections = (args: TExtensionProps) => ({ query }: { query: string }): TSlashCommandSection[] => { - const { additionalOptions, disabledExtensions } = args; + const { additionalOptions: externalAdditionalOptions, disabledExtensions } = args; const SLASH_COMMAND_SECTIONS: TSlashCommandSection[] = [ { key: "general", @@ -176,15 +176,6 @@ export const getSlashCommandFilteredSections = icon: , command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), }, - { - commandKey: "image", - key: "image", - title: "Image", - icon: , - description: "Insert an image", - searchTerms: ["img", "photo", "picture", "media", "upload"], - command: ({ editor, range }: CommandProps) => insertImage({ editor, event: "insert", range }), - }, { commandKey: "callout", key: "callout", @@ -284,8 +275,24 @@ export const getSlashCommandFilteredSections = }, ]; + const internalAdditionalOptions: TSlashCommandAdditionalOption[] = []; + if (!disabledExtensions?.includes("image")) { + internalAdditionalOptions.push({ + commandKey: "image", + key: "image", + title: "Image", + icon: , + description: "Insert an image", + searchTerms: ["img", "photo", "picture", "media", "upload"], + command: ({ editor, range }: CommandProps) => insertImage({ editor, event: "insert", range }), + section: "general", + pushAfter: "code", + }); + } + [ - ...(additionalOptions ?? []), + ...internalAdditionalOptions, + ...(externalAdditionalOptions ?? []), ...coreEditorAdditionalSlashCommandOptions({ disabledExtensions, }), diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index 16df9151caa..e520305ba8a 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -111,7 +111,7 @@ export const useEditor = (props: CustomEditorProps) => { // value is null when intentionally passed where syncing is not yet // supported and value is undefined when the data from swr is not populated if (value == null) return; - if (editor && !editor.isDestroyed && !editor.storage.imageComponent.uploadInProgress) { + if (editor && !editor.isDestroyed && !editor.storage.imageComponent?.uploadInProgress) { try { editor.commands.setContent(value, false, { preserveWhitespace: "full" }); if (editor.state.selection) { @@ -129,7 +129,7 @@ export const useEditor = (props: CustomEditorProps) => { useEffect(() => { if (!editor) return; const assetsUploadStatus = fileHandler.assetsUploadStatus; - editor.commands.updateAssetsUploadStatus(assetsUploadStatus); + editor.commands.updateAssetsUploadStatus?.(assetsUploadStatus); }, [editor, fileHandler.assetsUploadStatus]); useImperativeHandle( @@ -221,7 +221,7 @@ export const useEditor = (props: CustomEditorProps) => { if (!editor) return; scrollSummary(editor, marking); }, - isEditorReadyToDiscard: () => editor?.storage.imageComponent.uploadInProgress === false, + isEditorReadyToDiscard: () => editor?.storage.imageComponent?.uploadInProgress === false, setFocusAtPosition: (position: number) => { if (!editor || editor.isDestroyed) { console.error("Editor reference is not available or has been destroyed."); diff --git a/packages/editor/src/core/hooks/use-file-upload.ts b/packages/editor/src/core/hooks/use-file-upload.ts index 7d3dc7eaed9..e57c811a073 100644 --- a/packages/editor/src/core/hooks/use-file-upload.ts +++ b/packages/editor/src/core/hooks/use-file-upload.ts @@ -21,7 +21,9 @@ export const useUploader = (args: TUploaderArgs) => { const uploadFile = useCallback( async (file: File) => { const setImageUploadInProgress = (isUploading: boolean) => { - editor.storage.imageComponent.uploadInProgress = isUploading; + if (editor.storage.imageComponent) { + editor.storage.imageComponent.uploadInProgress = isUploading; + } }; setImageUploadInProgress(true); setUploading(true); diff --git a/packages/editor/src/core/types/extensions.ts b/packages/editor/src/core/types/extensions.ts index b3aacccc0af..8c1c0a48038 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" | "slash-commands" | "enter-key"; +export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed" | "slash-commands" | "enter-key" | "image";