From 6923225bfc6a8c87a789f773d3626cb5f72fd54b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 5 Mar 2025 17:58:49 +0530 Subject: [PATCH 1/2] chore: add disable image extension functionality --- .../custom-image/components/image-node.tsx | 2 +- .../custom-image/components/upload-status.tsx | 2 +- .../extensions/custom-image/custom-image.ts | 2 +- .../editor/src/core/extensions/extensions.tsx | 25 +++++--- .../image/image-component-without-props.tsx | 1 + .../core/extensions/read-only-extensions.tsx | 23 ++++--- .../slash-commands/command-items-list.tsx | 31 +++++---- packages/editor/src/core/hooks/use-editor.ts | 6 +- .../editor/src/core/hooks/use-file-upload.ts | 4 +- packages/editor/src/core/types/extensions.ts | 2 +- web/next.config.js | 63 +++++++++++++++++++ 11 files changed, 124 insertions(+), 37 deletions(-) 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"; diff --git a/web/next.config.js b/web/next.config.js index 672d9151b5f..64caced7946 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -75,6 +75,69 @@ const nextConfig = { } return rewrites; }, + experimental: { + optimizePackageImports: [ + "lucide-react", + // Components + "@/components/account", + "@/components/analytics", + "@/components/api-token", + "@/components/archives", + "@/components/auth-screens", + "@/components/automation", + "@/components/command-palette", + "@/components/common", + "@/components/core", + "@/components/cycles", + "@/components/dashboard", + "@/components/dropdowns", + "@/components/editor", + "@/components/empty-state", + "@/components/estimates", + "@/components/exporter", + "@/components/gantt-chart", + "@/components/gantt-chart/contexts", + "@/components/global", + "@/components/graphs", + "@/components/icons", + "@/components/inbox", + "@/components/instance", + "@/components/integration", + "@/components/issues", + "@/components/issues/issue-layouts", + "@/components/labels", + "@/components/modules", + "@/components/onboarding", + "@/components/page-views", + "@/components/pages", + "@/components/profile", + "@/components/project", + "@/components/project-states", + "@/components/sidebar", + "@/components/ui", + "@/components/user", + "@/components/views", + "@/components/web-hooks", + "@/components/workspace", + "@/components/workspace-notifications", + "@/plane-web/components/dashboards", + + // lib + "@/lib/store-context", + "@/lib/wrappers", + "@/lib/n-progress", + "@/lib/local-storage", + + // Services + "@/plane-web/services", + + // + "@headlessui/react", + "axios", + + "@/hooks/store", + ], + }, }; module.exports = nextConfig; From 70717e131b21c417535a3f93431c5a8c537b8e82 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 5 Mar 2025 18:20:18 +0530 Subject: [PATCH 2/2] chore: undo next.config changes --- web/next.config.js | 63 ---------------------------------------------- 1 file changed, 63 deletions(-) diff --git a/web/next.config.js b/web/next.config.js index 64caced7946..672d9151b5f 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -75,69 +75,6 @@ const nextConfig = { } return rewrites; }, - experimental: { - optimizePackageImports: [ - "lucide-react", - // Components - "@/components/account", - "@/components/analytics", - "@/components/api-token", - "@/components/archives", - "@/components/auth-screens", - "@/components/automation", - "@/components/command-palette", - "@/components/common", - "@/components/core", - "@/components/cycles", - "@/components/dashboard", - "@/components/dropdowns", - "@/components/editor", - "@/components/empty-state", - "@/components/estimates", - "@/components/exporter", - "@/components/gantt-chart", - "@/components/gantt-chart/contexts", - "@/components/global", - "@/components/graphs", - "@/components/icons", - "@/components/inbox", - "@/components/instance", - "@/components/integration", - "@/components/issues", - "@/components/issues/issue-layouts", - "@/components/labels", - "@/components/modules", - "@/components/onboarding", - "@/components/page-views", - "@/components/pages", - "@/components/profile", - "@/components/project", - "@/components/project-states", - "@/components/sidebar", - "@/components/ui", - "@/components/user", - "@/components/views", - "@/components/web-hooks", - "@/components/workspace", - "@/components/workspace-notifications", - "@/plane-web/components/dashboards", - - // lib - "@/lib/store-context", - "@/lib/wrappers", - "@/lib/n-progress", - "@/lib/local-storage", - - // Services - "@/plane-web/services", - - // - "@headlessui/react", - "axios", - - "@/hooks/store", - ], - }, }; module.exports = nextConfig;