From 16151e99d143a96714e134d8e7eb5b74c03c4666 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 16 May 2025 19:13:33 +0530 Subject: [PATCH 01/20] refactor: file plugins and types --- .../components/image-uploader.tsx | 2 + .../extensions/custom-image/custom-image.ts | 8 ++- .../src/core/extensions/image/extension.tsx | 8 ++- packages/editor/src/core/helpers/file.ts | 18 ++++-- .../editor/src/core/hooks/use-file-upload.ts | 25 ++++++-- .../{image/delete-image.ts => file/delete.ts} | 34 ++++++----- .../editor/src/core/plugins/file/restore.ts | 57 +++++++++++++++++ .../editor/src/core/plugins/file/types.ts | 8 +++ .../editor/src/core/plugins/image/index.ts | 2 - .../src/core/plugins/image/restore-image.ts | 61 ------------------- .../core/plugins/image/types/image-node.ts | 9 --- packages/editor/src/core/types/config.ts | 10 ++- packages/editor/src/core/types/image.ts | 5 -- 13 files changed, 133 insertions(+), 114 deletions(-) rename packages/editor/src/core/plugins/{image/delete-image.ts => file/delete.ts} (60%) create mode 100644 packages/editor/src/core/plugins/file/restore.ts create mode 100644 packages/editor/src/core/plugins/file/types.ts delete mode 100644 packages/editor/src/core/plugins/image/restore-image.ts delete mode 100644 packages/editor/src/core/types/image.ts 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 0a3ee1a1c36..8d00d7c0c93 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 @@ -85,6 +85,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES, editor, maxFileSize, + onInvalidFile: (_error, message) => alert(message), pos: getPos(), type: "image", uploader: uploadFile, @@ -123,6 +124,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { editor, filesList, maxFileSize, + onInvalidFile: (_error, message) => alert(message), pos: getPos(), type: "image", uploader: uploadFile, 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 a9a69fa60d3..2641f736f34 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -10,7 +10,8 @@ import { CustomImageNode } from "@/extensions/custom-image"; import { isFileValid } from "@/helpers/file"; import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary"; // plugins -import { TrackImageDeletionPlugin, TrackImageRestorationPlugin } from "@/plugins/image"; +import { TrackFileDeletionPlugin } from "@/plugins/file/delete"; +import { TrackFileRestorationPlugin } from "@/plugins/file/restore"; // types import { TFileHandler } from "@/types"; @@ -104,8 +105,8 @@ export const CustomImageExtension = (props: TFileHandler) => { addProseMirrorPlugins() { return [ - TrackImageDeletionPlugin(this.editor, deleteImageFn, this.name), - TrackImageRestorationPlugin(this.editor, restoreImageFn, this.name), + TrackFileDeletionPlugin(this.editor, deleteImageFn, this.name, "deletedImageSet"), + TrackFileRestorationPlugin(this.editor, restoreImageFn, this.name, "deletedImageSet"), ]; }, @@ -152,6 +153,7 @@ export const CustomImageExtension = (props: TFileHandler) => { acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES, file: props.file, maxFileSize, + onError: (_error, message) => alert(message), }) ) { return false; diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index 6766b4d0c03..ec73f9afacd 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -5,7 +5,9 @@ import { CustomImageNode } from "@/extensions"; // helpers import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary"; // plugins -import { ImageExtensionStorage, TrackImageDeletionPlugin, TrackImageRestorationPlugin } from "@/plugins/image"; +import { TrackFileDeletionPlugin } from "@/plugins/file/delete"; +import { TrackFileRestorationPlugin } from "@/plugins/file/restore"; +import { ImageExtensionStorage } from "@/plugins/image"; // types import { TFileHandler } from "@/types"; @@ -27,8 +29,8 @@ export const ImageExtension = (fileHandler: TFileHandler) => { addProseMirrorPlugins() { return [ - TrackImageDeletionPlugin(this.editor, deleteImageFn, this.name), - TrackImageRestorationPlugin(this.editor, restoreImageFn, this.name), + TrackFileDeletionPlugin(this.editor, deleteImageFn, this.name, "deletedImageSet"), + TrackFileRestorationPlugin(this.editor, restoreImageFn, this.name, "deletedImageSet"), ]; }, diff --git a/packages/editor/src/core/helpers/file.ts b/packages/editor/src/core/helpers/file.ts index f2c9968f003..33d3c7d781a 100644 --- a/packages/editor/src/core/helpers/file.ts +++ b/packages/editor/src/core/helpers/file.ts @@ -1,24 +1,34 @@ +export enum EFileError { + INVALID_FILE_TYPE = "INVALID_FILE_TYPE", + FILE_SIZE_TOO_LARGE = "FILE_SIZE_TOO_LARGE", + NO_FILE_SELECTED = "NO_FILE_SELECTED", +} + type TArgs = { acceptedMimeTypes: string[]; file: File; maxFileSize: number; + onError: (error: EFileError, message: string) => void; }; export const isFileValid = (args: TArgs): boolean => { - const { acceptedMimeTypes, file, maxFileSize } = args; + const { acceptedMimeTypes, file, maxFileSize, onError } = args; if (!file) { - alert("No file selected. Please select a file to upload."); + onError(EFileError.NO_FILE_SELECTED, "No file selected. Please select a file to upload."); return false; } if (!acceptedMimeTypes.includes(file.type)) { - alert("Invalid file type. Please select a JPEG, JPG, PNG, WEBP or GIF file."); + onError(EFileError.INVALID_FILE_TYPE, "Invalid file type."); return false; } if (file.size > maxFileSize) { - alert(`File size too large. Please select a file smaller than ${maxFileSize / 1024 / 1024}MB.`); + onError( + EFileError.FILE_SIZE_TOO_LARGE, + `File size too large. Please select a file smaller than ${maxFileSize / 1024 / 1024}MB.` + ); return false; } diff --git a/packages/editor/src/core/hooks/use-file-upload.ts b/packages/editor/src/core/hooks/use-file-upload.ts index b707824f260..245fab9f525 100644 --- a/packages/editor/src/core/hooks/use-file-upload.ts +++ b/packages/editor/src/core/hooks/use-file-upload.ts @@ -3,7 +3,7 @@ import { DragEvent, useCallback, useEffect, useState } from "react"; // extensions import { insertFilesSafely } from "@/extensions/drop"; // plugins -import { isFileValid } from "@/helpers/file"; +import { EFileError, isFileValid } from "@/helpers/file"; // types import { TEditorCommands } from "@/types"; @@ -13,12 +13,20 @@ type TUploaderArgs = { handleProgressStatus?: (isUploading: boolean) => void; loadFileFromFileSystem?: (file: string) => void; maxFileSize: number; + onInvalidFile: (error: EFileError, message: string) => void; onUpload: (url: string, file: File) => void; }; export const useUploader = (args: TUploaderArgs) => { - const { acceptedMimeTypes, editorCommand, handleProgressStatus, loadFileFromFileSystem, maxFileSize, onUpload } = - args; + const { + acceptedMimeTypes, + editorCommand, + handleProgressStatus, + loadFileFromFileSystem, + maxFileSize, + onInvalidFile, + onUpload, + } = args; // states const [isUploading, setIsUploading] = useState(false); @@ -30,6 +38,7 @@ export const useUploader = (args: TUploaderArgs) => { acceptedMimeTypes, file, maxFileSize, + onError: onInvalidFile, }); if (!isValid) { handleProgressStatus?.(false); @@ -75,13 +84,14 @@ type TDropzoneArgs = { acceptedMimeTypes: string[]; editor: Editor; maxFileSize: number; + onInvalidFile: (error: EFileError, message: string) => void; pos: number; type: Extract; uploader: (file: File) => Promise; }; export const useDropZone = (args: TDropzoneArgs) => { - const { acceptedMimeTypes, editor, maxFileSize, pos, type, uploader } = args; + const { acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader } = args; // states const [isDragging, setIsDragging] = useState(false); const [draggedInside, setDraggedInside] = useState(false); @@ -117,12 +127,13 @@ export const useDropZone = (args: TDropzoneArgs) => { editor, filesList, maxFileSize, + onInvalidFile, pos, type, uploader, }); }, - [acceptedMimeTypes, editor, maxFileSize, pos, type, uploader] + [acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader] ); const onDragEnter = useCallback(() => setDraggedInside(true), []); const onDragLeave = useCallback(() => setDraggedInside(false), []); @@ -141,6 +152,7 @@ type TMultipleFileArgs = { editor: Editor; filesList: FileList; maxFileSize: number; + onInvalidFile: (error: EFileError, message: string) => void; pos: number; type: Extract; uploader: (file: File) => Promise; @@ -148,7 +160,7 @@ type TMultipleFileArgs = { // Upload the first file and insert the remaining ones for uploading multiple files export const uploadFirstFileAndInsertRemaining = async (args: TMultipleFileArgs) => { - const { acceptedMimeTypes, editor, filesList, maxFileSize, pos, type, uploader } = args; + const { acceptedMimeTypes, editor, filesList, maxFileSize, onInvalidFile, pos, type, uploader } = args; const filteredFiles: File[] = []; for (let i = 0; i < filesList.length; i += 1) { const file = filesList.item(i); @@ -158,6 +170,7 @@ export const uploadFirstFileAndInsertRemaining = async (args: TMultipleFileArgs) acceptedMimeTypes, file, maxFileSize, + onError: onInvalidFile, }) ) { filteredFiles.push(file); diff --git a/packages/editor/src/core/plugins/image/delete-image.ts b/packages/editor/src/core/plugins/file/delete.ts similarity index 60% rename from packages/editor/src/core/plugins/image/delete-image.ts rename to packages/editor/src/core/plugins/file/delete.ts index 459d9fd7068..9008049e8d5 100644 --- a/packages/editor/src/core/plugins/image/delete-image.ts +++ b/packages/editor/src/core/plugins/file/delete.ts @@ -1,18 +1,23 @@ import { Editor } from "@tiptap/core"; import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; -// plugins -import { type ImageNode } from "@/plugins/image"; // types -import { DeleteImage } from "@/types"; +import { TFileHandler } from "@/types"; +// local imports +import { TFileNode } from "./types"; -export const TrackImageDeletionPlugin = (editor: Editor, deleteImage: DeleteImage, nodeType: string): Plugin => +export const TrackFileDeletionPlugin = ( + editor: Editor, + deleteHandler: TFileHandler["delete"], + nodeType: string, + fileSetName: string +): Plugin => new Plugin({ key: new PluginKey(`delete-${nodeType}`), appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { - const newImageSources = new Set(); + const newFileSources = new Set(); newState.doc.descendants((node) => { if (node.type.name === nodeType) { - newImageSources.add(node.attrs.src); + newFileSources.add(node.attrs.src); } }); @@ -22,27 +27,26 @@ export const TrackImageDeletionPlugin = (editor: Editor, deleteImage: DeleteImag // transaction could be a selection if (!transaction.docChanged) return; - const removedImages: ImageNode[] = []; + const removedFiles: TFileNode[] = []; // iterate through all the nodes in the old state oldState.doc.descendants((oldNode) => { - // if the node is not an image, then return as no point in checking + // if the node doesn't match, then return as no point in checking if (oldNode.type.name !== nodeType) return; - // Check if the node has been deleted or replaced - if (!newImageSources.has(oldNode.attrs.src)) { - removedImages.push(oldNode as ImageNode); + if (!newFileSources.has(oldNode.attrs.src)) { + removedFiles.push(oldNode as TFileNode); } }); - removedImages.forEach(async (node) => { + removedFiles.forEach(async (node) => { const src = node.attrs.src; - editor.storage[nodeType].deletedImageSet?.set(src, true); + editor.storage[nodeType][fileSetName]?.set(src, true); if (!src) return; try { - await deleteImage(src); + await deleteHandler(src); } catch (error) { - console.error("Error deleting image:", error); + console.error("Error deleting file:", error); } }); }); diff --git a/packages/editor/src/core/plugins/file/restore.ts b/packages/editor/src/core/plugins/file/restore.ts new file mode 100644 index 00000000000..71e35753ec9 --- /dev/null +++ b/packages/editor/src/core/plugins/file/restore.ts @@ -0,0 +1,57 @@ +import { Editor } from "@tiptap/core"; +import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; +// types +import { TFileHandler } from "@/types"; +// local imports +import { TFileNode } from "./types"; + +export const TrackFileRestorationPlugin = ( + editor: Editor, + restoreHandler: TFileHandler["restore"], + nodeType: string, + fileSetName: string +): Plugin => + new Plugin({ + key: new PluginKey(`restore-${nodeType}`), + appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { + const oldFileSources = new Set(); + oldState.doc.descendants((node) => { + if (node.type.name === nodeType) { + oldFileSources.add(node.attrs.src); + } + }); + + transactions.forEach((transaction) => { + if (!transaction.docChanged) return; + + const addedFiles: TFileNode[] = []; + + newState.doc.descendants((node, pos) => { + if (node.type.name !== nodeType) return; + if (pos < 0 || pos > newState.doc.content.size) return; + if (oldFileSources.has(node.attrs.src)) return; + // if the src is just a id (private bucket), then we don't need to handle restore from here but + // only while it fails to load + if (nodeType === "imageComponent" && !node.attrs.src?.startsWith("http")) return; + addedFiles.push(node as TFileNode); + }); + + addedFiles.forEach(async (node) => { + const src = node.attrs.src; + const wasDeleted = editor.storage[nodeType][fileSetName].get(src); + if (!src) return; + if (wasDeleted === undefined) { + editor.storage[nodeType][fileSetName].set(src, false); + } else if (wasDeleted === true) { + try { + await restoreHandler(src); + editor.storage[nodeType][fileSetName].set(src, false); + } catch (error) { + console.error("Error restoring file:", error); + } + } + }); + }); + return null; + }, + }); diff --git a/packages/editor/src/core/plugins/file/types.ts b/packages/editor/src/core/plugins/file/types.ts new file mode 100644 index 00000000000..164d12ae7ee --- /dev/null +++ b/packages/editor/src/core/plugins/file/types.ts @@ -0,0 +1,8 @@ +import { Node as ProseMirrorNode } from "@tiptap/pm/model"; + +export type TFileNode = ProseMirrorNode & { + attrs: { + src: string; + id: string; + }; +}; diff --git a/packages/editor/src/core/plugins/image/index.ts b/packages/editor/src/core/plugins/image/index.ts index c0dc631c533..eea524d6557 100644 --- a/packages/editor/src/core/plugins/image/index.ts +++ b/packages/editor/src/core/plugins/image/index.ts @@ -1,3 +1 @@ export * from "./types"; -export * from "./delete-image"; -export * from "./restore-image"; diff --git a/packages/editor/src/core/plugins/image/restore-image.ts b/packages/editor/src/core/plugins/image/restore-image.ts deleted file mode 100644 index 4eecf01d7e2..00000000000 --- a/packages/editor/src/core/plugins/image/restore-image.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Editor } from "@tiptap/core"; -import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; -// plugins -import { ImageNode } from "@/plugins/image"; -// types -import { RestoreImage } from "@/types"; - -export const TrackImageRestorationPlugin = (editor: Editor, restoreImage: RestoreImage, nodeType: string): Plugin => - new Plugin({ - key: new PluginKey(`restore-${nodeType}`), - appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { - const oldImageSources = new Set(); - oldState.doc.descendants((node) => { - if (node.type.name === nodeType) { - oldImageSources.add(node.attrs.src); - } - }); - - transactions.forEach((transaction) => { - if (!transaction.docChanged) return; - - const addedImages: ImageNode[] = []; - - newState.doc.descendants((node, pos) => { - if (node.type.name !== nodeType) return; - if (pos < 0 || pos > newState.doc.content.size) return; - if (oldImageSources.has(node.attrs.src)) return; - // if the src is just a id (private bucket), then we don't need to handle restore from here but - // only while it fails to load - if (!node.attrs.src?.startsWith("http")) return; - addedImages.push(node as ImageNode); - }); - - addedImages.forEach(async (image) => { - const src = image.attrs.src; - const wasDeleted = editor.storage[nodeType].deletedImageSet.get(src); - if (wasDeleted === undefined) { - editor.storage[nodeType].deletedImageSet.set(src, false); - } else if (wasDeleted === true) { - try { - await onNodeRestored(src, restoreImage); - editor.storage[nodeType].deletedImageSet.set(src, false); - } catch (error) { - console.error("Error restoring image: ", error); - } - } - }); - }); - return null; - }, - }); - -async function onNodeRestored(src: string, restoreImage: RestoreImage): Promise { - if (!src) return; - try { - await restoreImage(src); - } catch (error) { - console.error("Error restoring image: ", error); - throw error; - } -} diff --git a/packages/editor/src/core/plugins/image/types/image-node.ts b/packages/editor/src/core/plugins/image/types/image-node.ts index 67afc8315a4..9c703f86202 100644 --- a/packages/editor/src/core/plugins/image/types/image-node.ts +++ b/packages/editor/src/core/plugins/image/types/image-node.ts @@ -1,12 +1,3 @@ -import { Node as ProseMirrorNode } from "@tiptap/pm/model"; - -export interface ImageNode extends ProseMirrorNode { - attrs: { - src: string; - id: string; - }; -} - export type ImageExtensionStorage = { deletedImageSet: Map; uploadInProgress: boolean; diff --git a/packages/editor/src/core/types/config.ts b/packages/editor/src/core/types/config.ts index 4c91fec5d10..b72e3dcf654 100644 --- a/packages/editor/src/core/types/config.ts +++ b/packages/editor/src/core/types/config.ts @@ -1,19 +1,17 @@ -import { DeleteImage, RestoreImage, UploadImage } from "@/types"; - export type TReadOnlyFileHandler = { getAssetSrc: (path: string) => Promise; - restore: RestoreImage; + restore: (assetSrc: string) => Promise; }; export type TFileHandler = TReadOnlyFileHandler & { assetsUploadStatus: Record; // blockId => progress percentage cancel: () => void; - delete: DeleteImage; - upload: UploadImage; + delete: (assetSrc: string) => Promise; + upload: (blockId: string, file: File) => Promise; validation: { /** * @description max file size in bytes - * @example enter 5242880( 5* 1024 * 1024) for 5MB + * @example enter 5242880(5 * 1024 * 1024) for 5MB */ maxFileSize: number; }; diff --git a/packages/editor/src/core/types/image.ts b/packages/editor/src/core/types/image.ts deleted file mode 100644 index ca6f76fb1b8..00000000000 --- a/packages/editor/src/core/types/image.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise; - -export type RestoreImage = (assetUrlWithWorkspaceId: string) => Promise; - -export type UploadImage = (blockId: string, file: File) => Promise; From c0424213445043454f7030701eabb065ed6e3f3e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 19 May 2025 13:18:54 +0530 Subject: [PATCH 02/20] refactor: image extension storage types --- packages/editor/src/ce/types/storage.ts | 2 +- packages/editor/src/core/extensions/image/extension.tsx | 6 +++++- .../core/extensions/image/image-component-without-props.tsx | 4 ++-- packages/editor/src/core/plugins/image/index.ts | 1 - packages/editor/src/core/plugins/image/types/image-node.ts | 4 ---- packages/editor/src/core/plugins/image/types/index.ts | 1 - 6 files changed, 8 insertions(+), 10 deletions(-) delete mode 100644 packages/editor/src/core/plugins/image/index.ts delete mode 100644 packages/editor/src/core/plugins/image/types/image-node.ts delete mode 100644 packages/editor/src/core/plugins/image/types/index.ts diff --git a/packages/editor/src/ce/types/storage.ts b/packages/editor/src/ce/types/storage.ts index 4e106738b52..d986c432942 100644 --- a/packages/editor/src/ce/types/storage.ts +++ b/packages/editor/src/ce/types/storage.ts @@ -1,8 +1,8 @@ import { HeadingExtensionStorage } from "@/extensions"; import { CustomImageExtensionStorage } from "@/extensions/custom-image"; import { CustomLinkStorage } from "@/extensions/custom-link"; +import { ImageExtensionStorage } from "@/extensions/image"; import { MentionExtensionStorage } from "@/extensions/mentions"; -import { ImageExtensionStorage } from "@/plugins/image"; export type ExtensionStorageMap = { imageComponent: CustomImageExtensionStorage; diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index ec73f9afacd..5fe8070c622 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -7,10 +7,14 @@ import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-par // plugins import { TrackFileDeletionPlugin } from "@/plugins/file/delete"; import { TrackFileRestorationPlugin } from "@/plugins/file/restore"; -import { ImageExtensionStorage } from "@/plugins/image"; // types import { TFileHandler } from "@/types"; +export type ImageExtensionStorage = { + deletedImageSet: Map; + uploadInProgress: boolean; +}; + export const ImageExtension = (fileHandler: TFileHandler) => { const { getAssetSrc, 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 c17bcc5598e..d9012cd02e5 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 @@ -1,7 +1,7 @@ import { mergeAttributes } from "@tiptap/core"; import { Image } from "@tiptap/extension-image"; -// extensions -import { ImageExtensionStorage } from "@/plugins/image"; +// local imports +import { ImageExtensionStorage } from "./extension"; export const CustomImageComponentWithoutProps = () => Image.extend, ImageExtensionStorage>({ diff --git a/packages/editor/src/core/plugins/image/index.ts b/packages/editor/src/core/plugins/image/index.ts deleted file mode 100644 index eea524d6557..00000000000 --- a/packages/editor/src/core/plugins/image/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./types"; diff --git a/packages/editor/src/core/plugins/image/types/image-node.ts b/packages/editor/src/core/plugins/image/types/image-node.ts deleted file mode 100644 index 9c703f86202..00000000000 --- a/packages/editor/src/core/plugins/image/types/image-node.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ImageExtensionStorage = { - deletedImageSet: Map; - uploadInProgress: boolean; -}; diff --git a/packages/editor/src/core/plugins/image/types/index.ts b/packages/editor/src/core/plugins/image/types/index.ts deleted file mode 100644 index 2fddf3bf646..00000000000 --- a/packages/editor/src/core/plugins/image/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./image-node"; From 07bc6139921efc8f8193ab2c524db9bc90a1e4ff Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 19 May 2025 13:24:22 +0530 Subject: [PATCH 03/20] chore: update meta tag name --- packages/editor/src/core/hooks/use-editor.ts | 2 +- packages/editor/src/core/hooks/use-read-only-editor.ts | 2 +- packages/editor/src/core/plugins/file/delete.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index cf9d04d83e1..4d54cfcad3e 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -143,7 +143,7 @@ export const useEditor = (props: CustomEditorProps) => { }, getCurrentCursorPosition: () => editor?.state.selection.from, clearEditor: (emitUpdate = false) => { - editor?.chain().setMeta("skipImageDeletion", true).clearContent(emitUpdate).run(); + editor?.chain().setMeta("skipFileDeletion", true).clearContent(emitUpdate).run(); }, setEditorValue: (content: string, emitUpdate = false) => { editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" }); diff --git a/packages/editor/src/core/hooks/use-read-only-editor.ts b/packages/editor/src/core/hooks/use-read-only-editor.ts index b50b56b02dc..4762eff3e17 100644 --- a/packages/editor/src/core/hooks/use-read-only-editor.ts +++ b/packages/editor/src/core/hooks/use-read-only-editor.ts @@ -75,7 +75,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => { useImperativeHandle(forwardedRef, () => ({ clearEditor: (emitUpdate = false) => { - editor?.chain().setMeta("skipImageDeletion", true).clearContent(emitUpdate).run(); + editor?.chain().setMeta("skipFileDeletion", true).clearContent(emitUpdate).run(); }, setEditorValue: (content: string, emitUpdate = false) => { editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" }); diff --git a/packages/editor/src/core/plugins/file/delete.ts b/packages/editor/src/core/plugins/file/delete.ts index 9008049e8d5..8e18ee55620 100644 --- a/packages/editor/src/core/plugins/file/delete.ts +++ b/packages/editor/src/core/plugins/file/delete.ts @@ -22,8 +22,8 @@ export const TrackFileDeletionPlugin = ( }); transactions.forEach((transaction) => { - // if the transaction has meta of skipImageDeletion get to true, then return (like while clearing the editor content programatically) - if (transaction.getMeta("skipImageDeletion")) return; + // if the transaction has meta of skipFileDeletion get to true, then return (like while clearing the editor content programatically) + if (transaction.getMeta("skipFileDeletion")) return; // transaction could be a selection if (!transaction.docChanged) return; From 27de6f217ceb97713bbf5a3518a52aa8249e755b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 19 May 2025 13:33:53 +0530 Subject: [PATCH 04/20] chore: extension fileset storage key --- packages/editor/src/ce/types/storage.ts | 2 ++ packages/editor/src/core/plugins/file/delete.ts | 4 +++- packages/editor/src/core/plugins/file/restore.ts | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/ce/types/storage.ts b/packages/editor/src/ce/types/storage.ts index d986c432942..367796b9c76 100644 --- a/packages/editor/src/ce/types/storage.ts +++ b/packages/editor/src/ce/types/storage.ts @@ -11,3 +11,5 @@ export type ExtensionStorageMap = { headingList: HeadingExtensionStorage; mention: MentionExtensionStorage; }; + +export type ExtensionFileSetStorageKey = Extract; diff --git a/packages/editor/src/core/plugins/file/delete.ts b/packages/editor/src/core/plugins/file/delete.ts index 8e18ee55620..028a70c4db3 100644 --- a/packages/editor/src/core/plugins/file/delete.ts +++ b/packages/editor/src/core/plugins/file/delete.ts @@ -1,5 +1,7 @@ import { Editor } from "@tiptap/core"; import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; +// plane editor types +import { ExtensionFileSetStorageKey } from "@/plane-editor/types/storage"; // types import { TFileHandler } from "@/types"; // local imports @@ -9,7 +11,7 @@ export const TrackFileDeletionPlugin = ( editor: Editor, deleteHandler: TFileHandler["delete"], nodeType: string, - fileSetName: string + fileSetName: ExtensionFileSetStorageKey ): Plugin => new Plugin({ key: new PluginKey(`delete-${nodeType}`), diff --git a/packages/editor/src/core/plugins/file/restore.ts b/packages/editor/src/core/plugins/file/restore.ts index 71e35753ec9..5167ba4c6c0 100644 --- a/packages/editor/src/core/plugins/file/restore.ts +++ b/packages/editor/src/core/plugins/file/restore.ts @@ -1,5 +1,7 @@ import { Editor } from "@tiptap/core"; import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; +// plane editor types +import { ExtensionFileSetStorageKey } from "@/plane-editor/types/storage"; // types import { TFileHandler } from "@/types"; // local imports @@ -9,7 +11,7 @@ export const TrackFileRestorationPlugin = ( editor: Editor, restoreHandler: TFileHandler["restore"], nodeType: string, - fileSetName: string + fileSetName: ExtensionFileSetStorageKey ): Plugin => new Plugin({ key: new PluginKey(`restore-${nodeType}`), From 2223f822fa42bbb1e576c6c8b74933f7ae2f62fd Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 19 May 2025 13:39:40 +0530 Subject: [PATCH 05/20] fix: build errors --- packages/editor/src/core/types/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/editor/src/core/types/index.ts b/packages/editor/src/core/types/index.ts index e99a74b28ee..66cb249425b 100644 --- a/packages/editor/src/core/types/index.ts +++ b/packages/editor/src/core/types/index.ts @@ -4,7 +4,6 @@ export * from "./config"; export * from "./editor"; export * from "./embed"; export * from "./extensions"; -export * from "./image"; export * from "./mention"; export * from "./slash-commands-suggestion"; export * from "@/plane-editor/types"; From 4b3cc93fe48582806f32fca5a238b63170430d36 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 21 May 2025 18:31:44 +0530 Subject: [PATCH 06/20] refactor: utility extension --- packages/editor/src/ce/constants/utility.ts | 14 +++++ packages/editor/src/ce/types/storage.ts | 25 ++++---- .../components/editors/editor-container.tsx | 13 +++-- .../editor/src/core/constants/extension.ts | 44 ++++++++++++++ .../extensions/callout/extension-config.ts | 8 ++- .../src/core/extensions/code-inline/index.tsx | 6 +- .../src/core/extensions/code/code-block.ts | 6 +- .../src/core/extensions/custom-code-inline.ts | 9 --- .../src/core/extensions/custom-color.ts | 5 +- .../custom-image/components/image-node.tsx | 5 +- .../components/image-uploader.tsx | 2 +- .../custom-image/components/upload-status.tsx | 6 +- .../extensions/custom-image/custom-image.ts | 28 ++------- .../custom-image/read-only-custom-image.ts | 6 +- .../core/extensions/custom-link/extension.tsx | 5 +- .../core/extensions/enter-key-extension.tsx | 13 +++-- .../editor/src/core/extensions/extensions.tsx | 10 +++- .../editor/src/core/extensions/headers.ts | 4 +- .../src/core/extensions/horizontal-rule.ts | 6 +- .../src/core/extensions/image/extension.tsx | 13 ----- .../image/image-component-without-props.tsx | 2 - packages/editor/src/core/extensions/index.ts | 2 +- .../issue-embed/issue-embed-without-props.ts | 4 +- .../editor/src/core/extensions/side-menu.tsx | 4 +- .../core/extensions/slash-commands/root.tsx | 4 +- .../extensions/table/table-cell/table-cell.ts | 5 +- .../table/table-header/table-header.ts | 5 +- .../extensions/table/table-row/table-row.ts | 4 +- .../src/core/extensions/table/table/table.ts | 16 +++--- .../src/core/extensions/typography/index.ts | 4 +- .../editor/src/core/extensions/utility.ts | 51 +++++++++++++++++ packages/editor/src/core/hooks/use-editor.ts | 34 +++++++---- .../editor/src/core/plugins/file/delete.ts | 51 ++++++++++------- .../editor/src/core/plugins/file/restore.ts | 57 +++++++++++-------- web/core/store/pages/base-page.ts | 1 - 35 files changed, 307 insertions(+), 165 deletions(-) create mode 100644 packages/editor/src/ce/constants/utility.ts create mode 100644 packages/editor/src/core/constants/extension.ts delete mode 100644 packages/editor/src/core/extensions/custom-code-inline.ts create mode 100644 packages/editor/src/core/extensions/utility.ts diff --git a/packages/editor/src/ce/constants/utility.ts b/packages/editor/src/ce/constants/utility.ts new file mode 100644 index 00000000000..616838a6268 --- /dev/null +++ b/packages/editor/src/ce/constants/utility.ts @@ -0,0 +1,14 @@ +import { ExtensionFileSetStorageKey } from "@/plane-editor/types/storage"; + +export const NODE_FILE_MAP: { + [key: string]: { + fileSetName: ExtensionFileSetStorageKey; + }; +} = { + image: { + fileSetName: "deletedImageSet", + }, + imageComponent: { + fileSetName: "deletedImageSet", + }, +}; diff --git a/packages/editor/src/ce/types/storage.ts b/packages/editor/src/ce/types/storage.ts index 367796b9c76..5f576df5090 100644 --- a/packages/editor/src/ce/types/storage.ts +++ b/packages/editor/src/ce/types/storage.ts @@ -1,15 +1,20 @@ -import { HeadingExtensionStorage } from "@/extensions"; -import { CustomImageExtensionStorage } from "@/extensions/custom-image"; -import { CustomLinkStorage } from "@/extensions/custom-link"; -import { ImageExtensionStorage } from "@/extensions/image"; -import { MentionExtensionStorage } from "@/extensions/mentions"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; +// extensions +import { type HeadingExtensionStorage } from "@/extensions"; +import { type CustomImageExtensionStorage } from "@/extensions/custom-image"; +import { type CustomLinkStorage } from "@/extensions/custom-link"; +import { type ImageExtensionStorage } from "@/extensions/image"; +import { type MentionExtensionStorage } from "@/extensions/mentions"; +import { type UtilityExtensionStorage } from "@/extensions/utility"; export type ExtensionStorageMap = { - imageComponent: CustomImageExtensionStorage; - image: ImageExtensionStorage; - link: CustomLinkStorage; - headingList: HeadingExtensionStorage; - mention: MentionExtensionStorage; + [CORE_EXTENSIONS.CUSTOM_IMAGE]: CustomImageExtensionStorage; + [CORE_EXTENSIONS.IMAGE]: ImageExtensionStorage; + [CORE_EXTENSIONS.CUSTOM_LINK]: CustomLinkStorage; + [CORE_EXTENSIONS.HEADINGS_LIST]: HeadingExtensionStorage; + [CORE_EXTENSIONS.MENTION]: MentionExtensionStorage; + [CORE_EXTENSIONS.UTILITY]: UtilityExtensionStorage; }; export type ExtensionFileSetStorageKey = Extract; diff --git a/packages/editor/src/core/components/editors/editor-container.tsx b/packages/editor/src/core/components/editors/editor-container.tsx index d0811cd410c..8a069d612a7 100644 --- a/packages/editor/src/core/components/editors/editor-container.tsx +++ b/packages/editor/src/core/components/editors/editor-container.tsx @@ -4,6 +4,7 @@ import { FC, ReactNode, useRef } from "react"; import { cn } from "@plane/utils"; // constants import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config"; +import { CORE_EXTENSIONS } from "@/constants/extension"; // types import { TDisplayConfig } from "@/types"; // components @@ -36,12 +37,12 @@ export const EditorContainer: FC = (props) => { if ( currentNode.content.size === 0 && // Check if the current node is empty !( - editor.isActive("orderedList") || - editor.isActive("bulletList") || - editor.isActive("taskItem") || - editor.isActive("table") || - editor.isActive("blockquote") || - editor.isActive("codeBlock") + editor.isActive(CORE_EXTENSIONS.ORDERED_LIST) || + editor.isActive(CORE_EXTENSIONS.BULLET_LIST) || + editor.isActive(CORE_EXTENSIONS.TASK_ITEM) || + editor.isActive(CORE_EXTENSIONS.TABLE) || + editor.isActive(CORE_EXTENSIONS.BLOCKQUOTE) || + editor.isActive(CORE_EXTENSIONS.CODE_BLOCK) ) // Check if it's an empty node within an orderedList, bulletList, taskItem, table, quote or code block ) { return; diff --git a/packages/editor/src/core/constants/extension.ts b/packages/editor/src/core/constants/extension.ts new file mode 100644 index 00000000000..b8e6ca9afd5 --- /dev/null +++ b/packages/editor/src/core/constants/extension.ts @@ -0,0 +1,44 @@ +export enum CORE_EXTENSIONS { + BLOCKQUOTE = "blockquote", + BOLD = "bold", + BULLET_LIST = "bulletList", + CALLOUT = "calloutComponent", + CHARACTER_COUNT = "characterCount", + CODE_BLOCK = "codeBlock", + CODE_INLINE = "code", + CUSTOM_COLOR = "customColor", + CUSTOM_IMAGE = "imageComponent", + CUSTOM_LINK = "link", + DOCUMENT = "doc", + DROP_CURSOR = "dropCursor", + ENTER_KEY = "enterKey", + GAP_CURSOR = "gapCursor", + HARD_BREAK = "hardBreak", + HEADING = "heading", + HEADINGS_LIST = "headingList", + HISTORY = "history", + HORIZONTAL_RULE = "horizontalRule", + IMAGE = "image", + ITALIC = "italic", + LIST_ITEM = "listItem", + MARKDOWN_CLIPBOARD = "markdownClipboard", + MENTION = "mention", + ORDERED_LIST = "orderedList", + PARAGRAPH = "paragraph", + PLACEHOLDER = "placeholder", + SIDE_MENU = "editorSideMenu", + SLASH_COMMANDS = "slash-command", + STRIKETHROUGH = "strike", + TABLE = "table", + TABLE_CELL = "tableCell", + TABLE_HEADER = "tableHeader", + TABLE_ROW = "tableRow", + TASK_ITEM = "taskItem", + TASK_LIST = "taskList", + TEXT_ALIGN = "textAlign", + TEXT_STYLE = "textStyle", + TYPOGRAPHY = "typography", + UNDERLINE = "underline", + UTILITY = "utility", + WORK_ITEM_EMBED = "issue-embed-component", +} diff --git a/packages/editor/src/core/extensions/callout/extension-config.ts b/packages/editor/src/core/extensions/callout/extension-config.ts index 546311509ea..e52be72d650 100644 --- a/packages/editor/src/core/extensions/callout/extension-config.ts +++ b/packages/editor/src/core/extensions/callout/extension-config.ts @@ -1,6 +1,8 @@ import { Node, mergeAttributes } from "@tiptap/core"; -import { Node as NodeType } from "@tiptap/pm/model"; import { MarkdownSerializerState } from "@tiptap/pm/markdown"; +import { Node as NodeType } from "@tiptap/pm/model"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // types import { EAttributeNames, TCalloutBlockAttributes } from "./types"; // utils @@ -9,14 +11,14 @@ import { DEFAULT_CALLOUT_BLOCK_ATTRIBUTES } from "./utils"; // Extend Tiptap's Commands interface declare module "@tiptap/core" { interface Commands { - calloutComponent: { + [CORE_EXTENSIONS.CALLOUT]: { insertCallout: () => ReturnType; }; } } export const CustomCalloutExtensionConfig = Node.create({ - name: "calloutComponent", + name: CORE_EXTENSIONS.CALLOUT, group: "block", content: "block+", diff --git a/packages/editor/src/core/extensions/code-inline/index.tsx b/packages/editor/src/core/extensions/code-inline/index.tsx index 6e023b6ed16..ae320cf6a29 100644 --- a/packages/editor/src/core/extensions/code-inline/index.tsx +++ b/packages/editor/src/core/extensions/code-inline/index.tsx @@ -1,4 +1,6 @@ import { Mark, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export interface CodeOptions { HTMLAttributes: Record; @@ -6,7 +8,7 @@ export interface CodeOptions { declare module "@tiptap/core" { interface Commands { - code: { + [CORE_EXTENSIONS.CODE_INLINE]: { /** * Set a code mark */ @@ -27,7 +29,7 @@ export const inputRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))$/; const pasteRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))/g; export const CustomCodeInlineExtension = Mark.create({ - name: "code", + name: CORE_EXTENSIONS.CODE_INLINE, addOptions() { return { diff --git a/packages/editor/src/core/extensions/code/code-block.ts b/packages/editor/src/core/extensions/code/code-block.ts index b2218ee45ce..8bc0c8d2d27 100644 --- a/packages/editor/src/core/extensions/code/code-block.ts +++ b/packages/editor/src/core/extensions/code/code-block.ts @@ -1,5 +1,7 @@ import { mergeAttributes, Node, textblockTypeInputRule } from "@tiptap/core"; import { Plugin, PluginKey } from "@tiptap/pm/state"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export interface CodeBlockOptions { /** @@ -25,7 +27,7 @@ export interface CodeBlockOptions { declare module "@tiptap/core" { interface Commands { - codeBlock: { + [CORE_EXTENSIONS.CODE_BLOCK]: { /** * Set a code block */ @@ -42,7 +44,7 @@ export const backtickInputRegex = /^```([a-z]+)?[\s\n]$/; export const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/; export const CodeBlock = Node.create({ - name: "codeBlock", + name: CORE_EXTENSIONS.CODE_BLOCK, addOptions() { return { diff --git a/packages/editor/src/core/extensions/custom-code-inline.ts b/packages/editor/src/core/extensions/custom-code-inline.ts deleted file mode 100644 index 3b3cfaab1e1..00000000000 --- a/packages/editor/src/core/extensions/custom-code-inline.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Extension } from "@tiptap/core"; -import codemark from "prosemirror-codemark"; - -export const CustomCodeMarkPlugin = Extension.create({ - name: "codemarkPlugin", - addProseMirrorPlugins() { - return codemark({ markType: this.editor.schema.marks.code }); - }, -}); diff --git a/packages/editor/src/core/extensions/custom-color.ts b/packages/editor/src/core/extensions/custom-color.ts index b377099fb59..8b516e8ecd3 100644 --- a/packages/editor/src/core/extensions/custom-color.ts +++ b/packages/editor/src/core/extensions/custom-color.ts @@ -1,10 +1,11 @@ import { Mark, mergeAttributes } from "@tiptap/core"; // constants import { COLORS_LIST } from "@/constants/common"; +import { CORE_EXTENSIONS } from "@/constants/extension"; declare module "@tiptap/core" { interface Commands { - color: { + [CORE_EXTENSIONS.CUSTOM_COLOR]: { /** * Set the text color * @param {string} color The color to set @@ -34,7 +35,7 @@ declare module "@tiptap/core" { } export const CustomColorExtension = Mark.create({ - name: "customColor", + name: CORE_EXTENSIONS.CUSTOM_COLOR, addOptions() { return { 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 e525bc6da4b..f8bfcf4a1f4 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 @@ -1,7 +1,10 @@ import { Editor, NodeViewProps, NodeViewWrapper } from "@tiptap/react"; import { useEffect, useRef, useState } from "react"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { CustomImageBlock, CustomImageUploader, ImageAttributes } from "@/extensions/custom-image"; +// helpers import { getExtensionStorage } from "@/helpers/get-extension-storage"; export type CustoBaseImageNodeViewProps = { @@ -77,7 +80,7 @@ export const CustomImageNode = (props: CustomImageNodeProps) => { failedToLoadImage={failedToLoadImage} getPos={getPos} loadImageFromFileSystem={setImageFromFileSystem} - maxFileSize={getExtensionStorage(editor, "imageComponent").maxFileSize} + maxFileSize={getExtensionStorage(editor, CORE_EXTENSIONS.CUSTOM_IMAGE).maxFileSize} node={node} setIsUploaded={setIsUploaded} selected={selected} 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 8d00d7c0c93..ccecf16181b 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 @@ -75,7 +75,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { // @ts-expect-error - TODO: fix typings, and don't remove await from here for now editorCommand: async (file) => await editor?.commands.uploadImage(imageEntityId, file), handleProgressStatus: (isUploading) => { - editor.storage.imageComponent.uploadInProgress = isUploading; + editor.storage.utility.uploadInProgress = isUploading; }, loadFileFromFileSystem: loadImageFromFileSystem, maxFileSize, 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 8b71713d24a..f88c69c6f37 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 @@ -1,6 +1,10 @@ import { Editor } from "@tiptap/core"; import { useEditorState } from "@tiptap/react"; import { useEffect, useRef, useState } from "react"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; +// helpers +import { getExtensionStorage } from "@/helpers/get-extension-storage"; type Props = { editor: Editor; @@ -16,7 +20,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 }) => getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.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 acbe691a775..3c9c4d3c568 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -4,15 +4,13 @@ import { ReactNodeViewRenderer } from "@tiptap/react"; import { v4 as uuidv4 } from "uuid"; // constants import { ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config"; +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { CustomImageNode } from "@/extensions/custom-image"; // helpers import { isFileValid } from "@/helpers/file"; import { getExtensionStorage } from "@/helpers/get-extension-storage"; import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary"; -// plugins -import { TrackFileDeletionPlugin } from "@/plugins/file/delete"; -import { TrackFileRestorationPlugin } from "@/plugins/file/restore"; // types import { TFileHandler } from "@/types"; @@ -24,23 +22,21 @@ export type InsertImageComponentProps = { declare module "@tiptap/core" { interface Commands { - imageComponent: { + [CORE_EXTENSIONS.CUSTOM_IMAGE]: { insertImageComponent: ({ file, pos, event }: InsertImageComponentProps) => ReturnType; uploadImage: (blockId: string, file: File) => () => Promise | undefined; - updateAssetsUploadStatus?: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void; getImageSource?: (path: string) => () => Promise; restoreImage: (src: string) => () => Promise; }; } } -export const getImageComponentImageFileMap = (editor: Editor) => getExtensionStorage(editor, "imageComponent")?.fileMap; +export const getImageComponentImageFileMap = (editor: Editor) => + getExtensionStorage(editor, CORE_EXTENSIONS.CUSTOM_IMAGE)?.fileMap; export interface CustomImageExtensionStorage { - assetsUploadStatus: TFileHandler["assetsUploadStatus"]; fileMap: Map; deletedImageSet: Map; - uploadInProgress: boolean; maxFileSize: number; } @@ -48,16 +44,14 @@ export type UploadEntity = ({ event: "insert" } | { event: "drop"; file: File }) export const CustomImageExtension = (props: TFileHandler) => { const { - assetsUploadStatus, getAssetSrc, upload, - delete: deleteImageFn, restore: restoreImageFn, validation: { maxFileSize }, } = props; return Image.extend, CustomImageExtensionStorage>({ - name: "imageComponent", + name: CORE_EXTENSIONS.CUSTOM_IMAGE, selectable: true, group: "block", atom: true, @@ -103,13 +97,6 @@ export const CustomImageExtension = (props: TFileHandler) => { }; }, - addProseMirrorPlugins() { - return [ - TrackFileDeletionPlugin(this.editor, deleteImageFn, this.name, "deletedImageSet"), - TrackFileRestorationPlugin(this.editor, restoreImageFn, this.name, "deletedImageSet"), - ]; - }, - onCreate(this) { const imageSources = new Set(); this.editor.state.doc.descendants((node) => { @@ -131,13 +118,11 @@ export const CustomImageExtension = (props: TFileHandler) => { return { fileMap: new Map(), deletedImageSet: new Map(), - uploadInProgress: false, maxFileSize, // escape markdown for images markdown: { serialize() {}, }, - assetsUploadStatus, }; }, @@ -198,9 +183,6 @@ export const CustomImageExtension = (props: TFileHandler) => { const fileUrl = await upload(blockId, file); return fileUrl; }, - updateAssetsUploadStatus: (updatedStatus) => () => { - this.storage.assetsUploadStatus = updatedStatus; - }, getImageSource: (path) => async () => await getAssetSrc(path), restoreImage: (src) => async () => { await restoreImageFn(src); 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 51b758898b2..c1af38ec1af 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 @@ -1,6 +1,8 @@ import { mergeAttributes } from "@tiptap/core"; import { Image } from "@tiptap/extension-image"; import { ReactNodeViewRenderer } from "@tiptap/react"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // components import { CustomImageNode, CustomImageExtensionStorage } from "@/extensions/custom-image"; // types @@ -10,7 +12,7 @@ export const CustomReadOnlyImageExtension = (props: TReadOnlyFileHandler) => { const { getAssetSrc, restore: restoreImageFn } = props; return Image.extend, CustomImageExtensionStorage>({ - name: "imageComponent", + name: CORE_EXTENSIONS.CUSTOM_IMAGE, selectable: false, group: "block", atom: true, @@ -53,13 +55,11 @@ export const CustomReadOnlyImageExtension = (props: TReadOnlyFileHandler) => { return { fileMap: new Map(), deletedImageSet: new Map(), - uploadInProgress: false, maxFileSize: 0, // escape markdown for images markdown: { serialize() {}, }, - assetsUploadStatus: {}, }; }, diff --git a/packages/editor/src/core/extensions/custom-link/extension.tsx b/packages/editor/src/core/extensions/custom-link/extension.tsx index 27c1bb598da..9cc6462d6e4 100644 --- a/packages/editor/src/core/extensions/custom-link/extension.tsx +++ b/packages/editor/src/core/extensions/custom-link/extension.tsx @@ -1,6 +1,9 @@ import { Mark, markPasteRule, mergeAttributes, PasteRuleMatch } from "@tiptap/core"; import { Plugin } from "@tiptap/pm/state"; import { find, registerCustomProtocol, reset } from "linkifyjs"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; +// local imports import { autolink } from "./helpers/autolink"; import { clickHandler } from "./helpers/clickHandler"; import { pasteHandler } from "./helpers/pasteHandler"; @@ -79,7 +82,7 @@ export type CustomLinkStorage = { }; export const CustomLinkExtension = Mark.create({ - name: "link", + name: CORE_EXTENSIONS.CUSTOM_LINK, priority: 1000, diff --git a/packages/editor/src/core/extensions/enter-key-extension.tsx b/packages/editor/src/core/extensions/enter-key-extension.tsx index d67ceb78b8e..6e8014c84e5 100644 --- a/packages/editor/src/core/extensions/enter-key-extension.tsx +++ b/packages/editor/src/core/extensions/enter-key-extension.tsx @@ -1,16 +1,19 @@ import { Extension } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; +// helpers +import { getExtensionStorage } from "@/helpers/get-extension-storage"; export const EnterKeyExtension = (onEnterKeyPress?: () => void) => Extension.create({ - name: "enterKey", + name: CORE_EXTENSIONS.ENTER_KEY, addKeyboardShortcuts(this) { return { Enter: () => { - if (!this.editor.storage.mentionsOpen) { - if (onEnterKeyPress) { - onEnterKeyPress(); - } + const isMentionOpen = getExtensionStorage(this.editor, CORE_EXTENSIONS.MENTION)?.mentionsOpen; + if (!isMentionOpen) { + onEnterKeyPress?.(); return true; } return false; diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 1ef0a3b157c..95f423c006b 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -7,12 +7,13 @@ import TextStyle from "@tiptap/extension-text-style"; import TiptapUnderline from "@tiptap/extension-underline"; import StarterKit from "@tiptap/starter-kit"; import { Markdown } from "tiptap-markdown"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { CustomCalloutExtension, CustomCodeBlockExtension, CustomCodeInlineExtension, - CustomCodeMarkPlugin, CustomColorExtension, CustomHorizontalRule, CustomImageExtension, @@ -30,9 +31,11 @@ import { TableHeader, TableRow, MarkdownClipboard, + UtilityExtension, } from "@/extensions"; // helpers import { isValidHttpUrl } from "@/helpers/common"; +import { getExtensionStorage } from "@/helpers/get-extension-storage"; // plane editor extensions import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions"; // types @@ -127,7 +130,6 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { class: "", }, }), - CustomCodeMarkPlugin, CustomCodeInlineExtension, Markdown.configure({ html: true, @@ -147,7 +149,8 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { if (node.type.name === "heading") return `Heading ${node.attrs.level}`; - if (editor.storage.imageComponent?.uploadInProgress) return ""; + const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress; + if (isUploadInProgress) return ""; const shouldHidePlaceholder = editor.isActive("table") || @@ -169,6 +172,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { CharacterCount, CustomTextAlignExtension, CustomCalloutExtension, + UtilityExtension(fileHandler), CustomColorExtension, ...CoreEditorAdditionalExtensions({ disabledExtensions, diff --git a/packages/editor/src/core/extensions/headers.ts b/packages/editor/src/core/extensions/headers.ts index 958cf6ca32b..a1a9303a08e 100644 --- a/packages/editor/src/core/extensions/headers.ts +++ b/packages/editor/src/core/extensions/headers.ts @@ -1,5 +1,7 @@ import { Extension } from "@tiptap/core"; import { Plugin, PluginKey } from "@tiptap/pm/state"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export interface IMarking { type: "heading"; @@ -13,7 +15,7 @@ export type HeadingExtensionStorage = { }; export const HeadingListExtension = Extension.create({ - name: "headingList", + name: CORE_EXTENSIONS.HEADINGS_LIST, addStorage() { return { diff --git a/packages/editor/src/core/extensions/horizontal-rule.ts b/packages/editor/src/core/extensions/horizontal-rule.ts index b9be1a314df..99a5dacc3ef 100644 --- a/packages/editor/src/core/extensions/horizontal-rule.ts +++ b/packages/editor/src/core/extensions/horizontal-rule.ts @@ -1,5 +1,7 @@ import { isNodeSelection, mergeAttributes, Node, nodeInputRule } from "@tiptap/core"; import { NodeSelection, TextSelection } from "@tiptap/pm/state"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export interface HorizontalRuleOptions { HTMLAttributes: Record; @@ -7,7 +9,7 @@ export interface HorizontalRuleOptions { declare module "@tiptap/core" { interface Commands { - horizontalRule: { + [CORE_EXTENSIONS.HORIZONTAL_RULE]: { /** * Add a horizontal rule */ @@ -17,7 +19,7 @@ declare module "@tiptap/core" { } export const CustomHorizontalRule = Node.create({ - name: "horizontalRule", + name: CORE_EXTENSIONS.HORIZONTAL_RULE, addOptions() { return { diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index 5fe8070c622..ffcb941f930 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -4,21 +4,16 @@ import { ReactNodeViewRenderer } from "@tiptap/react"; import { CustomImageNode } from "@/extensions"; // helpers import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary"; -// plugins -import { TrackFileDeletionPlugin } from "@/plugins/file/delete"; -import { TrackFileRestorationPlugin } from "@/plugins/file/restore"; // types import { TFileHandler } from "@/types"; export type ImageExtensionStorage = { deletedImageSet: Map; - uploadInProgress: boolean; }; export const ImageExtension = (fileHandler: TFileHandler) => { const { getAssetSrc, - delete: deleteImageFn, restore: restoreImageFn, validation: { maxFileSize }, } = fileHandler; @@ -31,13 +26,6 @@ export const ImageExtension = (fileHandler: TFileHandler) => { }; }, - addProseMirrorPlugins() { - return [ - TrackFileDeletionPlugin(this.editor, deleteImageFn, this.name, "deletedImageSet"), - TrackFileRestorationPlugin(this.editor, restoreImageFn, this.name, "deletedImageSet"), - ]; - }, - onCreate(this) { const imageSources = new Set(); this.editor.state.doc.descendants((node) => { @@ -60,7 +48,6 @@ export const ImageExtension = (fileHandler: TFileHandler) => { addStorage() { return { deletedImageSet: new Map(), - uploadInProgress: false, maxFileSize, }; }, 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 d9012cd02e5..7d214556e91 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,9 +48,7 @@ export const CustomImageComponentWithoutProps = () => return { fileMap: new Map(), deletedImageSet: new Map(), - uploadInProgress: false, maxFileSize: 0, - assetsUploadStatus: {}, }; }, }); diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts index e9860758520..1a4c48f1270 100644 --- a/packages/editor/src/core/extensions/index.ts +++ b/packages/editor/src/core/extensions/index.ts @@ -11,7 +11,6 @@ export * from "./slash-commands"; export * from "./table"; export * from "./typography"; export * from "./core-without-props"; -export * from "./custom-code-inline"; export * from "./custom-color"; export * from "./drop"; export * from "./enter-key-extension"; @@ -24,3 +23,4 @@ export * from "./read-only-extensions"; export * from "./side-menu"; export * from "./text-align"; export * from "./clipboard"; +export * from "./utility"; diff --git a/packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts b/packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts index bef366cbab0..db7c0c8c1b8 100644 --- a/packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts +++ b/packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts @@ -1,8 +1,10 @@ import { mergeAttributes, Node } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export const IssueWidgetWithoutProps = () => Node.create({ - name: "issue-embed-component", + name: CORE_EXTENSIONS.WORK_ITEM_EMBED, group: "block", atom: true, selectable: true, diff --git a/packages/editor/src/core/extensions/side-menu.tsx b/packages/editor/src/core/extensions/side-menu.tsx index 5f11286b5c4..34e3c45e5f2 100644 --- a/packages/editor/src/core/extensions/side-menu.tsx +++ b/packages/editor/src/core/extensions/side-menu.tsx @@ -1,6 +1,8 @@ import { Extension } from "@tiptap/core"; import { Plugin, PluginKey } from "@tiptap/pm/state"; import { EditorView } from "@tiptap/pm/view"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // plugins import { AIHandlePlugin } from "@/plugins/ai-handle"; import { DragHandlePlugin, nodeDOMAtCoords } from "@/plugins/drag-handle"; @@ -33,7 +35,7 @@ export const SideMenuExtension = (props: Props) => { const { aiEnabled, dragDropEnabled } = props; return Extension.create({ - name: "editorSideMenu", + name: CORE_EXTENSIONS.SIDE_MENU, addProseMirrorPlugins() { return [ SideMenu({ diff --git a/packages/editor/src/core/extensions/slash-commands/root.tsx b/packages/editor/src/core/extensions/slash-commands/root.tsx index c0c078a2dda..98f5e8ab65c 100644 --- a/packages/editor/src/core/extensions/slash-commands/root.tsx +++ b/packages/editor/src/core/extensions/slash-commands/root.tsx @@ -2,6 +2,8 @@ import { Editor, Range, Extension } from "@tiptap/core"; import { ReactRenderer } from "@tiptap/react"; import Suggestion, { SuggestionOptions } from "@tiptap/suggestion"; import tippy from "tippy.js"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // helpers import { CommandListInstance } from "@/helpers/tippy"; // types @@ -20,7 +22,7 @@ export type TSlashCommandAdditionalOption = ISlashCommandItem & { }; const Command = Extension.create({ - name: "slash-command", + name: CORE_EXTENSIONS.SLASH_COMMANDS, addOptions() { return { suggestion: { diff --git a/packages/editor/src/core/extensions/table/table-cell/table-cell.ts b/packages/editor/src/core/extensions/table/table-cell/table-cell.ts index 403bd3f02c7..2ba06845a6c 100644 --- a/packages/editor/src/core/extensions/table/table-cell/table-cell.ts +++ b/packages/editor/src/core/extensions/table/table-cell/table-cell.ts @@ -1,11 +1,12 @@ import { mergeAttributes, Node } from "@tiptap/core"; - +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export interface TableCellOptions { HTMLAttributes: Record; } export const TableCell = Node.create({ - name: "tableCell", + name: CORE_EXTENSIONS.TABLE_CELL, addOptions() { return { diff --git a/packages/editor/src/core/extensions/table/table-header/table-header.ts b/packages/editor/src/core/extensions/table/table-header/table-header.ts index bd994f467d5..491889eefae 100644 --- a/packages/editor/src/core/extensions/table/table-header/table-header.ts +++ b/packages/editor/src/core/extensions/table/table-header/table-header.ts @@ -1,11 +1,12 @@ import { mergeAttributes, Node } from "@tiptap/core"; - +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export interface TableHeaderOptions { HTMLAttributes: Record; } export const TableHeader = Node.create({ - name: "tableHeader", + name: CORE_EXTENSIONS.TABLE_HEADER, addOptions() { return { diff --git a/packages/editor/src/core/extensions/table/table-row/table-row.ts b/packages/editor/src/core/extensions/table/table-row/table-row.ts index f961c058246..48f95a41c93 100644 --- a/packages/editor/src/core/extensions/table/table-row/table-row.ts +++ b/packages/editor/src/core/extensions/table/table-row/table-row.ts @@ -1,11 +1,13 @@ import { mergeAttributes, Node } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export interface TableRowOptions { HTMLAttributes: Record; } export const TableRow = Node.create({ - name: "tableRow", + name: CORE_EXTENSIONS.TABLE_ROW, addOptions() { return { diff --git a/packages/editor/src/core/extensions/table/table/table.ts b/packages/editor/src/core/extensions/table/table/table.ts index fd775d211ee..22b6232efae 100644 --- a/packages/editor/src/core/extensions/table/table/table.ts +++ b/packages/editor/src/core/extensions/table/table/table.ts @@ -19,11 +19,13 @@ import { toggleHeader, toggleHeaderCell, } from "@tiptap/pm/tables"; - -import { tableControls } from "@/extensions/table/table/table-controls"; -import { TableView } from "@/extensions/table/table/table-view"; -import { createTable } from "@/extensions/table/table/utilities/create-table"; -import { deleteTableWhenAllCellsSelected } from "@/extensions/table/table/utilities/delete-table-when-all-cells-selected"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; +// local imports +import { tableControls } from "./table-controls"; +import { TableView } from "./table-view"; +import { createTable } from "./utilities/create-table"; +import { deleteTableWhenAllCellsSelected } from "./utilities/delete-table-when-all-cells-selected"; import { insertLineAboveTableAction } from "./utilities/insert-line-above-table-action"; import { insertLineBelowTableAction } from "./utilities/insert-line-below-table-action"; @@ -38,7 +40,7 @@ export interface TableOptions { declare module "@tiptap/core" { interface Commands { - table: { + [CORE_EXTENSIONS.TABLE]: { insertTable: (options?: { rows?: number; cols?: number; @@ -79,7 +81,7 @@ declare module "@tiptap/core" { } export const Table = Node.create({ - name: "table", + name: CORE_EXTENSIONS.TABLE, addOptions() { return { diff --git a/packages/editor/src/core/extensions/typography/index.ts b/packages/editor/src/core/extensions/typography/index.ts index 6b736953b53..32ffea6a24c 100644 --- a/packages/editor/src/core/extensions/typography/index.ts +++ b/packages/editor/src/core/extensions/typography/index.ts @@ -1,4 +1,6 @@ import { Extension, InputRule } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; import { TypographyOptions, emDash, @@ -23,7 +25,7 @@ import { } from "./inputRules"; export const CustomTypographyExtension = Extension.create({ - name: "typography", + name: CORE_EXTENSIONS.TYPOGRAPHY, addInputRules() { const rules: InputRule[] = []; diff --git a/packages/editor/src/core/extensions/utility.ts b/packages/editor/src/core/extensions/utility.ts new file mode 100644 index 00000000000..8a1ceda6955 --- /dev/null +++ b/packages/editor/src/core/extensions/utility.ts @@ -0,0 +1,51 @@ +import { Extension } from "@tiptap/core"; +import codemark from "prosemirror-codemark"; +// plugins +import { TrackFileDeletionPlugin } from "@/plugins/file/delete"; +import { TrackFileRestorationPlugin } from "@/plugins/file/restore"; +// types +import { TFileHandler } from "@/types"; + +declare module "@tiptap/core" { + interface Commands { + utility: { + updateAssetsUploadStatus: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void; + }; + } +} + +export interface UtilityExtensionStorage { + assetsUploadStatus: TFileHandler["assetsUploadStatus"]; + uploadInProgress: boolean; +} + +export const UtilityExtension = (props: TFileHandler) => { + const { assetsUploadStatus, delete: deleteFileHandler, restore: restoreFileHandler } = props; + + return Extension.create, UtilityExtensionStorage>({ + name: "utility", + + addProseMirrorPlugins() { + return [ + TrackFileDeletionPlugin(this.editor, deleteFileHandler), + TrackFileRestorationPlugin(this.editor, restoreFileHandler), + ...codemark({ markType: this.editor.schema.marks.code }), + ]; + }, + + addStorage() { + return { + assetsUploadStatus, + uploadInProgress: false, + }; + }, + + addCommands() { + return { + updateAssetsUploadStatus: (updatedStatus) => () => { + this.storage.assetsUploadStatus = updatedStatus; + }, + }; + }, + }); +}; diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index 4d54cfcad3e..c34527fa57e 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -6,10 +6,13 @@ import { useImperativeHandle, MutableRefObject, useEffect } from "react"; import * as Y from "yjs"; // components import { getEditorMenuItems } from "@/components/menus"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { CoreEditorExtensions } from "@/extensions"; // helpers import { getParagraphCount } from "@/helpers/common"; +import { getExtensionStorage } from "@/helpers/get-extension-storage"; import { insertContentAtSavedSelection } from "@/helpers/insert-content-at-cursor-position"; import { IMarking, scrollSummary, scrollToNodeViaDOMCoordinates } from "@/helpers/scroll-to-node"; // props @@ -111,16 +114,19 @@ 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) { - try { - editor.commands.setContent(value, false, { preserveWhitespace: "full" }); - if (editor.state.selection) { - const docLength = editor.state.doc.content.size; - const relativePosition = Math.min(editor.state.selection.from, docLength - 1); - editor.commands.setTextSelection(relativePosition); + if (editor) { + const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress; + if (!editor.isDestroyed && !isUploadInProgress) { + try { + editor.commands.setContent(value, false, { preserveWhitespace: "full" }); + if (editor.state.selection) { + const docLength = editor.state.doc.content.size; + const relativePosition = Math.min(editor.state.selection.from, docLength - 1); + editor.commands.setTextSelection(relativePosition); + } + } catch (error) { + console.error("Error syncing editor content with external value:", error); } - } catch (error) { - console.error("Error syncing editor content with external value:", error); } } }, [editor, value, id]); @@ -179,7 +185,10 @@ export const useEditor = (props: CustomEditorProps) => { onHeadingChange: (callback: (headings: IMarking[]) => void) => { // Subscribe to update event emitted from headers extension editor?.on("update", () => { - callback(editor?.storage.headingList.headings); + const headings = getExtensionStorage(editor, CORE_EXTENSIONS.HEADINGS_LIST)?.headings; + if (headings) { + callback(headings); + } }); // Return a function to unsubscribe to the continuous transactions of // the editor on unmounting the component that has subscribed to this @@ -188,7 +197,7 @@ export const useEditor = (props: CustomEditorProps) => { editor?.off("update"); }; }, - getHeadings: () => editor?.storage.headingList.headings, + getHeadings: () => (editor ? getExtensionStorage(editor, CORE_EXTENSIONS.HEADINGS_LIST)?.headings : []), onStateChange: (callback: () => void) => { // Subscribe to editor state changes editor?.on("transaction", () => { @@ -221,7 +230,8 @@ export const useEditor = (props: CustomEditorProps) => { if (!editor) return; scrollSummary(editor, marking); }, - isEditorReadyToDiscard: () => editor?.storage.imageComponent?.uploadInProgress === false, + isEditorReadyToDiscard: () => + !!editor && getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.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/plugins/file/delete.ts b/packages/editor/src/core/plugins/file/delete.ts index 028a70c4db3..2a12794e466 100644 --- a/packages/editor/src/core/plugins/file/delete.ts +++ b/packages/editor/src/core/plugins/file/delete.ts @@ -1,54 +1,61 @@ import { Editor } from "@tiptap/core"; import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; -// plane editor types -import { ExtensionFileSetStorageKey } from "@/plane-editor/types/storage"; +// plane editor imports +import { NODE_FILE_MAP } from "@/plane-editor/constants/utility"; // types import { TFileHandler } from "@/types"; // local imports import { TFileNode } from "./types"; -export const TrackFileDeletionPlugin = ( - editor: Editor, - deleteHandler: TFileHandler["delete"], - nodeType: string, - fileSetName: ExtensionFileSetStorageKey -): Plugin => +export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHandler["delete"]): Plugin => new Plugin({ - key: new PluginKey(`delete-${nodeType}`), + key: new PluginKey("delete-utility"), appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { - const newFileSources = new Set(); + const newFileSources: { + [nodeType: string]: Set | undefined; + } = {}; + if (!transactions.some((tr) => tr.docChanged)) return null; + newState.doc.descendants((node) => { - if (node.type.name === nodeType) { - newFileSources.add(node.attrs.src); + const nodeType = node.type.name; + const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; + if (nodeFileSetDetails) { + if (newFileSources[nodeType]) { + newFileSources[nodeType].add(node.attrs.src); + } else { + newFileSources[nodeType] = new Set([node.attrs.src]); + } } }); transactions.forEach((transaction) => { - // if the transaction has meta of skipFileDeletion get to true, then return (like while clearing the editor content programatically) + // if the transaction has meta of skipFileDeletion set to true, then return (like while clearing the editor content programmatically) if (transaction.getMeta("skipFileDeletion")) return; - // transaction could be a selection - if (!transaction.docChanged) return; const removedFiles: TFileNode[] = []; // iterate through all the nodes in the old state - oldState.doc.descendants((oldNode) => { + oldState.doc.descendants((node) => { + const nodeType = node.type.name; + const isAValidNode = NODE_FILE_MAP[nodeType]; // if the node doesn't match, then return as no point in checking - if (oldNode.type.name !== nodeType) return; + if (!isAValidNode) return; // Check if the node has been deleted or replaced - if (!newFileSources.has(oldNode.attrs.src)) { - removedFiles.push(oldNode as TFileNode); + if (!newFileSources[nodeType]?.has(node.attrs.src)) { + removedFiles.push(node as TFileNode); } }); removedFiles.forEach(async (node) => { + const nodeType = node.type.name; const src = node.attrs.src; - editor.storage[nodeType][fileSetName]?.set(src, true); - if (!src) return; + const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; + if (!nodeFileSetDetails || !src) return; try { + editor.storage[nodeType][nodeFileSetDetails.fileSetName]?.set(src, true); await deleteHandler(src); } catch (error) { - console.error("Error deleting file:", error); + console.error("Error deleting file via delete utility plugin:", error); } }); }); diff --git a/packages/editor/src/core/plugins/file/restore.ts b/packages/editor/src/core/plugins/file/restore.ts index 5167ba4c6c0..d7f36225617 100644 --- a/packages/editor/src/core/plugins/file/restore.ts +++ b/packages/editor/src/core/plugins/file/restore.ts @@ -1,55 +1,66 @@ import { Editor } from "@tiptap/core"; import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; -// plane editor types -import { ExtensionFileSetStorageKey } from "@/plane-editor/types/storage"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; +// plane editor imports +import { NODE_FILE_MAP } from "@/plane-editor/constants/utility"; // types import { TFileHandler } from "@/types"; // local imports import { TFileNode } from "./types"; -export const TrackFileRestorationPlugin = ( - editor: Editor, - restoreHandler: TFileHandler["restore"], - nodeType: string, - fileSetName: ExtensionFileSetStorageKey -): Plugin => +export const TrackFileRestorationPlugin = (editor: Editor, restoreHandler: TFileHandler["restore"]): Plugin => new Plugin({ - key: new PluginKey(`restore-${nodeType}`), + key: new PluginKey("restore-utility"), appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { - const oldFileSources = new Set(); + if (!transactions.some((tr) => tr.docChanged)) return null; + + const oldFileSources: { + [key: string]: Set | undefined; + } = {}; oldState.doc.descendants((node) => { - if (node.type.name === nodeType) { - oldFileSources.add(node.attrs.src); + const nodeType = node.type.name; + const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; + if (nodeFileSetDetails) { + if (oldFileSources[nodeType]) { + oldFileSources[nodeType].add(node.attrs.src); + } else { + oldFileSources[nodeType] = new Set([node.attrs.src]); + } } }); - transactions.forEach((transaction) => { - if (!transaction.docChanged) return; - + transactions.forEach(() => { const addedFiles: TFileNode[] = []; newState.doc.descendants((node, pos) => { - if (node.type.name !== nodeType) return; + const nodeType = node.type.name; + const isAValidNode = NODE_FILE_MAP[nodeType]; + // if the node doesn't match, then return as no point in checking + if (!isAValidNode) return; if (pos < 0 || pos > newState.doc.content.size) return; - if (oldFileSources.has(node.attrs.src)) return; + if (oldFileSources[nodeType]?.has(node.attrs.src)) return; // if the src is just a id (private bucket), then we don't need to handle restore from here but // only while it fails to load - if (nodeType === "imageComponent" && !node.attrs.src?.startsWith("http")) return; + if (nodeType === CORE_EXTENSIONS.CUSTOM_IMAGE && !node.attrs.src?.startsWith("http")) return; addedFiles.push(node as TFileNode); }); addedFiles.forEach(async (node) => { + const nodeType = node.type.name; const src = node.attrs.src; - const wasDeleted = editor.storage[nodeType][fileSetName].get(src); - if (!src) return; + const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; + const extensionFileSetStorage = editor.storage[nodeType]?.[nodeFileSetDetails.fileSetName]; + const wasDeleted = extensionFileSetStorage?.get(src); + if (!nodeFileSetDetails || !src) return; if (wasDeleted === undefined) { - editor.storage[nodeType][fileSetName].set(src, false); + extensionFileSetStorage?.set(src, false); } else if (wasDeleted === true) { try { await restoreHandler(src); - editor.storage[nodeType][fileSetName].set(src, false); + extensionFileSetStorage?.set(src, false); } catch (error) { - console.error("Error restoring file:", error); + console.error("Error restoring file via restore utility plugin:", error); } } }); diff --git a/web/core/store/pages/base-page.ts b/web/core/store/pages/base-page.ts index 32a087c95ff..6639e8e84dc 100644 --- a/web/core/store/pages/base-page.ts +++ b/web/core/store/pages/base-page.ts @@ -529,7 +529,6 @@ export class BasePage implements TBasePage { }; setEditorRef = (editorRef: EditorRefApi | null) => { - console.log("store editorRef", editorRef); runInAction(() => { this.editorRef = editorRef; }); From 87116691428f18c69c35c33a8d7b2e4676db3818 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 21 May 2025 18:51:25 +0530 Subject: [PATCH 07/20] refactor: file plugins --- .../editor/src/core/extensions/extensions.tsx | 5 +++- .../core/extensions/read-only-extensions.tsx | 5 ++++ .../editor/src/core/extensions/utility.ts | 23 ++++++++++++------- packages/editor/src/core/plugins/file/root.ts | 22 ++++++++++++++++++ 4 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 packages/editor/src/core/plugins/file/root.ts diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 95f423c006b..a329f7e9133 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -172,7 +172,10 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { CharacterCount, CustomTextAlignExtension, CustomCalloutExtension, - UtilityExtension(fileHandler), + UtilityExtension({ + isEditable: true, + fileHandler, + }), CustomColorExtension, ...CoreEditorAdditionalExtensions({ disabledExtensions, diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.tsx index 3881c548b3f..9b93f5cb3db 100644 --- a/packages/editor/src/core/extensions/read-only-extensions.tsx +++ b/packages/editor/src/core/extensions/read-only-extensions.tsx @@ -25,6 +25,7 @@ import { CustomCalloutReadOnlyExtension, CustomColorExtension, MarkdownClipboard, + UtilityExtension, } from "@/extensions"; // helpers import { isValidHttpUrl } from "@/helpers/common"; @@ -127,6 +128,10 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { CustomColorExtension, CustomTextAlignExtension, CustomCalloutReadOnlyExtension, + UtilityExtension({ + isEditable: false, + fileHandler, + }), ...CoreReadOnlyEditorAdditionalExtensions({ disabledExtensions, }), diff --git a/packages/editor/src/core/extensions/utility.ts b/packages/editor/src/core/extensions/utility.ts index 8a1ceda6955..9c351734e97 100644 --- a/packages/editor/src/core/extensions/utility.ts +++ b/packages/editor/src/core/extensions/utility.ts @@ -1,10 +1,9 @@ import { Extension } from "@tiptap/core"; import codemark from "prosemirror-codemark"; // plugins -import { TrackFileDeletionPlugin } from "@/plugins/file/delete"; -import { TrackFileRestorationPlugin } from "@/plugins/file/restore"; +import { FilePlugins } from "@/plugins/file/root"; // types -import { TFileHandler } from "@/types"; +import { TFileHandler, TReadOnlyFileHandler } from "@/types"; declare module "@tiptap/core" { interface Commands { @@ -19,23 +18,31 @@ export interface UtilityExtensionStorage { uploadInProgress: boolean; } -export const UtilityExtension = (props: TFileHandler) => { - const { assetsUploadStatus, delete: deleteFileHandler, restore: restoreFileHandler } = props; +type Props = { + fileHandler: TFileHandler | TReadOnlyFileHandler; + isEditable: boolean; +}; + +export const UtilityExtension = (props: Props) => { + const { fileHandler, isEditable } = props; return Extension.create, UtilityExtensionStorage>({ name: "utility", addProseMirrorPlugins() { return [ - TrackFileDeletionPlugin(this.editor, deleteFileHandler), - TrackFileRestorationPlugin(this.editor, restoreFileHandler), + ...FilePlugins({ + editor: this.editor, + isEditable, + fileHandler, + }), ...codemark({ markType: this.editor.schema.marks.code }), ]; }, addStorage() { return { - assetsUploadStatus, + assetsUploadStatus: isEditable && "assetsUploadStatus" in fileHandler ? fileHandler.assetsUploadStatus : {}, uploadInProgress: false, }; }, diff --git a/packages/editor/src/core/plugins/file/root.ts b/packages/editor/src/core/plugins/file/root.ts new file mode 100644 index 00000000000..693ac6964ba --- /dev/null +++ b/packages/editor/src/core/plugins/file/root.ts @@ -0,0 +1,22 @@ +import { Editor } from "@tiptap/core"; +import { Plugin } from "@tiptap/pm/state"; +// types +import { TFileHandler, TReadOnlyFileHandler } from "@/types"; +// local imports +import { TrackFileDeletionPlugin } from "./delete"; +import { TrackFileRestorationPlugin } from "./restore"; + +type TArgs = { + editor: Editor; + fileHandler: TFileHandler | TReadOnlyFileHandler; + isEditable: boolean; +}; + +export const FilePlugins = (args: TArgs): Plugin[] => { + const { editor, fileHandler, isEditable } = args; + + return [ + ...(isEditable && "delete" in fileHandler ? [TrackFileDeletionPlugin(editor, fileHandler.delete)] : []), + TrackFileRestorationPlugin(editor, fileHandler.restore), + ]; +}; From d9a010396de86706da9eb75b03598782945d4c0a Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 21 May 2025 19:15:19 +0530 Subject: [PATCH 08/20] chore: remove standalone plugin extensions --- .../editor/src/core/extensions/clipboard.ts | 89 ------------ packages/editor/src/core/extensions/drop.ts | 127 ------------------ .../editor/src/core/extensions/extensions.tsx | 4 - packages/editor/src/core/extensions/index.ts | 2 - .../core/extensions/read-only-extensions.tsx | 2 - .../editor/src/core/extensions/utility.ts | 5 + .../editor/src/core/hooks/use-file-upload.ts | 6 +- packages/editor/src/core/plugins/drop.ts | 118 ++++++++++++++++ .../src/core/plugins/markdown-clipboard.ts | 78 +++++++++++ 9 files changed, 204 insertions(+), 227 deletions(-) delete mode 100644 packages/editor/src/core/extensions/clipboard.ts delete mode 100644 packages/editor/src/core/extensions/drop.ts create mode 100644 packages/editor/src/core/plugins/drop.ts create mode 100644 packages/editor/src/core/plugins/markdown-clipboard.ts diff --git a/packages/editor/src/core/extensions/clipboard.ts b/packages/editor/src/core/extensions/clipboard.ts deleted file mode 100644 index 252f0a113ff..00000000000 --- a/packages/editor/src/core/extensions/clipboard.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Extension } from "@tiptap/core"; -import { Fragment, Node } from "@tiptap/pm/model"; -import { Plugin, PluginKey } from "@tiptap/pm/state"; - -export const MarkdownClipboard = Extension.create({ - name: "markdownClipboard", - - addProseMirrorPlugins() { - return [ - new Plugin({ - key: new PluginKey("markdownClipboard"), - props: { - clipboardTextSerializer: (slice) => { - const markdownSerializer = this.editor.storage.markdown.serializer; - const isTableRow = slice.content.firstChild?.type?.name === "tableRow"; - const nodeSelect = slice.openStart === 0 && slice.openEnd === 0; - - if (nodeSelect) { - return markdownSerializer.serialize(slice.content); - } - - const processTableContent = (tableNode: Node | Fragment) => { - let result = ""; - tableNode.content?.forEach?.((tableRowNode: Node | Fragment) => { - tableRowNode.content?.forEach?.((cell: Node) => { - const cellContent = cell.content ? markdownSerializer.serialize(cell.content) : ""; - result += cellContent + "\n"; - }); - }); - return result; - }; - - if (isTableRow) { - const rowsCount = slice.content?.childCount || 0; - const cellsCount = slice.content?.firstChild?.content?.childCount || 0; - if (rowsCount === 1 || cellsCount === 1) { - return processTableContent(slice.content); - } else { - return markdownSerializer.serialize(slice.content); - } - } - - const traverseToParentOfLeaf = ( - node: Node | null, - parent: Fragment | Node, - depth: number - ): Node | Fragment => { - let currentNode = node; - let currentParent = parent; - let currentDepth = depth; - - while (currentNode && currentDepth > 1 && currentNode.content?.firstChild) { - if (currentNode.content?.childCount > 1) { - if (currentNode.content.firstChild?.type?.name === "listItem") { - return currentParent; - } else { - return currentNode.content; - } - } - - currentParent = currentNode; - currentNode = currentNode.content?.firstChild || null; - currentDepth--; - } - - return currentParent; - }; - - if (slice.content.childCount > 1) { - return markdownSerializer.serialize(slice.content); - } else { - const targetNode = traverseToParentOfLeaf(slice.content.firstChild, slice.content, slice.openStart); - - let currentNode = targetNode; - while (currentNode && currentNode.content && currentNode.childCount === 1 && currentNode.firstChild) { - currentNode = currentNode.firstChild; - } - if (currentNode instanceof Node && currentNode.isText) { - return currentNode.text; - } - - return markdownSerializer.serialize(targetNode); - } - }, - }, - }), - ]; - }, -}); diff --git a/packages/editor/src/core/extensions/drop.ts b/packages/editor/src/core/extensions/drop.ts deleted file mode 100644 index 2a5a994f8ab..00000000000 --- a/packages/editor/src/core/extensions/drop.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Extension, Editor } from "@tiptap/core"; -import { Plugin, PluginKey } from "@tiptap/pm/state"; -// constants -import { ACCEPTED_ATTACHMENT_MIME_TYPES, ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config"; -// types -import { TEditorCommands } from "@/types"; - -export const DropHandlerExtension = Extension.create({ - name: "dropHandler", - priority: 1000, - - addProseMirrorPlugins() { - const editor = this.editor; - return [ - new Plugin({ - key: new PluginKey("drop-handler-plugin"), - props: { - handlePaste: (view, event) => { - if ( - editor.isEditable && - event.clipboardData && - event.clipboardData.files && - event.clipboardData.files.length > 0 - ) { - event.preventDefault(); - const files = Array.from(event.clipboardData.files); - const acceptedFiles = files.filter( - (f) => ACCEPTED_IMAGE_MIME_TYPES.includes(f.type) || ACCEPTED_ATTACHMENT_MIME_TYPES.includes(f.type) - ); - - if (acceptedFiles.length) { - const pos = view.state.selection.from; - insertFilesSafely({ - editor, - files: acceptedFiles, - initialPos: pos, - event: "drop", - }); - } - return true; - } - return false; - }, - handleDrop: (view, event, _slice, moved) => { - if ( - editor.isEditable && - !moved && - event.dataTransfer && - event.dataTransfer.files && - event.dataTransfer.files.length > 0 - ) { - event.preventDefault(); - const files = Array.from(event.dataTransfer.files); - const acceptedFiles = files.filter( - (f) => ACCEPTED_IMAGE_MIME_TYPES.includes(f.type) || ACCEPTED_ATTACHMENT_MIME_TYPES.includes(f.type) - ); - - if (acceptedFiles.length) { - const coordinates = view.posAtCoords({ - left: event.clientX, - top: event.clientY, - }); - - if (coordinates) { - const pos = coordinates.pos; - insertFilesSafely({ - editor, - files: acceptedFiles, - initialPos: pos, - event: "drop", - }); - } - return true; - } - } - return false; - }, - }, - }), - ]; - }, -}); - -type InsertFilesSafelyArgs = { - editor: Editor; - event: "insert" | "drop"; - files: File[]; - initialPos: number; - type?: Extract; -}; - -export const insertFilesSafely = async (args: InsertFilesSafelyArgs) => { - const { editor, event, files, initialPos, type } = args; - let pos = initialPos; - - for (const file of files) { - // safe insertion - const docSize = editor.state.doc.content.size; - pos = Math.min(pos, docSize); - - let fileType: "image" | "attachment" | null = null; - - try { - if (type) { - if (["image", "attachment"].includes(type)) fileType = type; - else throw new Error("Wrong file type passed"); - } else { - if (ACCEPTED_IMAGE_MIME_TYPES.includes(file.type)) fileType = "image"; - else if (ACCEPTED_ATTACHMENT_MIME_TYPES.includes(file.type)) fileType = "attachment"; - } - // insert file depending on the type at the current position - if (fileType === "image") { - editor.commands.insertImageComponent({ - file, - pos, - event, - }); - } else if (fileType === "attachment") { - } - } catch (error) { - console.error(`Error while ${event}ing file:`, error); - } - - // Move to the next position - pos += 1; - } -}; diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index a329f7e9133..e9c32912716 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -23,14 +23,12 @@ import { CustomQuoteExtension, CustomTextAlignExtension, CustomTypographyExtension, - DropHandlerExtension, ImageExtension, ListKeymap, Table, TableCell, TableHeader, TableRow, - MarkdownClipboard, UtilityExtension, } from "@/extensions"; // helpers @@ -92,7 +90,6 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { ...(enableHistory ? {} : { history: false }), }), CustomQuoteExtension, - DropHandlerExtension, CustomHorizontalRule.configure({ HTMLAttributes: { class: "py-4 border-custom-border-400", @@ -137,7 +134,6 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { transformPastedText: true, breaks: true, }), - MarkdownClipboard, Table, TableHeader, TableCell, diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts index 1a4c48f1270..4a0e9790f1b 100644 --- a/packages/editor/src/core/extensions/index.ts +++ b/packages/editor/src/core/extensions/index.ts @@ -12,7 +12,6 @@ export * from "./table"; export * from "./typography"; export * from "./core-without-props"; export * from "./custom-color"; -export * from "./drop"; export * from "./enter-key-extension"; export * from "./extensions"; export * from "./headers"; @@ -22,5 +21,4 @@ export * from "./quote"; export * from "./read-only-extensions"; export * from "./side-menu"; export * from "./text-align"; -export * from "./clipboard"; export * from "./utility"; diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.tsx index 9b93f5cb3db..bcfc7641159 100644 --- a/packages/editor/src/core/extensions/read-only-extensions.tsx +++ b/packages/editor/src/core/extensions/read-only-extensions.tsx @@ -24,7 +24,6 @@ import { CustomTextAlignExtension, CustomCalloutReadOnlyExtension, CustomColorExtension, - MarkdownClipboard, UtilityExtension, } from "@/extensions"; // helpers @@ -118,7 +117,6 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { html: true, transformCopiedText: false, }), - MarkdownClipboard, Table, TableHeader, TableCell, diff --git a/packages/editor/src/core/extensions/utility.ts b/packages/editor/src/core/extensions/utility.ts index 9c351734e97..d6058272a49 100644 --- a/packages/editor/src/core/extensions/utility.ts +++ b/packages/editor/src/core/extensions/utility.ts @@ -1,7 +1,9 @@ import { Extension } from "@tiptap/core"; import codemark from "prosemirror-codemark"; // plugins +import { DropHandlerPlugin } from "@/plugins/drop"; import { FilePlugins } from "@/plugins/file/root"; +import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard"; // types import { TFileHandler, TReadOnlyFileHandler } from "@/types"; @@ -28,6 +30,7 @@ export const UtilityExtension = (props: Props) => { return Extension.create, UtilityExtensionStorage>({ name: "utility", + priority: 1000, addProseMirrorPlugins() { return [ @@ -37,6 +40,8 @@ export const UtilityExtension = (props: Props) => { fileHandler, }), ...codemark({ markType: this.editor.schema.marks.code }), + MarkdownClipboardPlugin(this.editor), + DropHandlerPlugin(this.editor), ]; }, diff --git a/packages/editor/src/core/hooks/use-file-upload.ts b/packages/editor/src/core/hooks/use-file-upload.ts index 245fab9f525..e40c1591341 100644 --- a/packages/editor/src/core/hooks/use-file-upload.ts +++ b/packages/editor/src/core/hooks/use-file-upload.ts @@ -1,9 +1,9 @@ import { Editor } from "@tiptap/core"; import { DragEvent, useCallback, useEffect, useState } from "react"; -// extensions -import { insertFilesSafely } from "@/extensions/drop"; -// plugins +// helpers import { EFileError, isFileValid } from "@/helpers/file"; +// plugins +import { insertFilesSafely } from "@/plugins/drop"; // types import { TEditorCommands } from "@/types"; diff --git a/packages/editor/src/core/plugins/drop.ts b/packages/editor/src/core/plugins/drop.ts new file mode 100644 index 00000000000..a0bb65779fd --- /dev/null +++ b/packages/editor/src/core/plugins/drop.ts @@ -0,0 +1,118 @@ +import { Editor } from "@tiptap/core"; +import { Plugin, PluginKey } from "@tiptap/pm/state"; +// constants +import { ACCEPTED_ATTACHMENT_MIME_TYPES, ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config"; +// types +import { TEditorCommands } from "@/types"; + +export const DropHandlerPlugin = (editor: Editor): Plugin => + new Plugin({ + key: new PluginKey("drop-handler-plugin"), + props: { + handlePaste: (view, event) => { + if ( + editor.isEditable && + event.clipboardData && + event.clipboardData.files && + event.clipboardData.files.length > 0 + ) { + event.preventDefault(); + const files = Array.from(event.clipboardData.files); + const acceptedFiles = files.filter( + (f) => ACCEPTED_IMAGE_MIME_TYPES.includes(f.type) || ACCEPTED_ATTACHMENT_MIME_TYPES.includes(f.type) + ); + + if (acceptedFiles.length) { + const pos = view.state.selection.from; + insertFilesSafely({ + editor, + files: acceptedFiles, + initialPos: pos, + event: "drop", + }); + } + return true; + } + return false; + }, + handleDrop: (view, event, _slice, moved) => { + if ( + editor.isEditable && + !moved && + event.dataTransfer && + event.dataTransfer.files && + event.dataTransfer.files.length > 0 + ) { + event.preventDefault(); + const files = Array.from(event.dataTransfer.files); + const acceptedFiles = files.filter( + (f) => ACCEPTED_IMAGE_MIME_TYPES.includes(f.type) || ACCEPTED_ATTACHMENT_MIME_TYPES.includes(f.type) + ); + + if (acceptedFiles.length) { + const coordinates = view.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + + if (coordinates) { + const pos = coordinates.pos; + insertFilesSafely({ + editor, + files: acceptedFiles, + initialPos: pos, + event: "drop", + }); + } + return true; + } + } + return false; + }, + }, + }); + +type InsertFilesSafelyArgs = { + editor: Editor; + event: "insert" | "drop"; + files: File[]; + initialPos: number; + type?: Extract; +}; + +export const insertFilesSafely = async (args: InsertFilesSafelyArgs) => { + const { editor, event, files, initialPos, type } = args; + let pos = initialPos; + + for (const file of files) { + // safe insertion + const docSize = editor.state.doc.content.size; + pos = Math.min(pos, docSize); + + let fileType: "image" | "attachment" | null = null; + + try { + if (type) { + if (["image", "attachment"].includes(type)) fileType = type; + else throw new Error("Wrong file type passed"); + } else { + if (ACCEPTED_IMAGE_MIME_TYPES.includes(file.type)) fileType = "image"; + else if (ACCEPTED_ATTACHMENT_MIME_TYPES.includes(file.type)) fileType = "attachment"; + } + // insert file depending on the type at the current position + if (fileType === "image") { + editor.commands.insertImageComponent({ + file, + pos, + event, + }); + } else if (fileType === "attachment") { + } + } catch (error) { + console.error(`Error while ${event}ing file:`, error); + } + + // Move to the next position + pos += 1; + } +}; diff --git a/packages/editor/src/core/plugins/markdown-clipboard.ts b/packages/editor/src/core/plugins/markdown-clipboard.ts new file mode 100644 index 00000000000..72676abd184 --- /dev/null +++ b/packages/editor/src/core/plugins/markdown-clipboard.ts @@ -0,0 +1,78 @@ +import { Editor } from "@tiptap/core"; +import { Fragment, Node } from "@tiptap/pm/model"; +import { Plugin, PluginKey } from "@tiptap/pm/state"; + +export const MarkdownClipboardPlugin = (editor: Editor): Plugin => + new Plugin({ + key: new PluginKey("markdownClipboard"), + props: { + clipboardTextSerializer: (slice) => { + const markdownSerializer = editor.storage.markdown.serializer; + const isTableRow = slice.content.firstChild?.type?.name === "tableRow"; + const nodeSelect = slice.openStart === 0 && slice.openEnd === 0; + + if (nodeSelect) { + return markdownSerializer.serialize(slice.content); + } + + const processTableContent = (tableNode: Node | Fragment) => { + let result = ""; + tableNode.content?.forEach?.((tableRowNode: Node | Fragment) => { + tableRowNode.content?.forEach?.((cell: Node) => { + const cellContent = cell.content ? markdownSerializer.serialize(cell.content) : ""; + result += cellContent + "\n"; + }); + }); + return result; + }; + + if (isTableRow) { + const rowsCount = slice.content?.childCount || 0; + const cellsCount = slice.content?.firstChild?.content?.childCount || 0; + if (rowsCount === 1 || cellsCount === 1) { + return processTableContent(slice.content); + } else { + return markdownSerializer.serialize(slice.content); + } + } + + const traverseToParentOfLeaf = (node: Node | null, parent: Fragment | Node, depth: number): Node | Fragment => { + let currentNode = node; + let currentParent = parent; + let currentDepth = depth; + + while (currentNode && currentDepth > 1 && currentNode.content?.firstChild) { + if (currentNode.content?.childCount > 1) { + if (currentNode.content.firstChild?.type?.name === "listItem") { + return currentParent; + } else { + return currentNode.content; + } + } + + currentParent = currentNode; + currentNode = currentNode.content?.firstChild || null; + currentDepth--; + } + + return currentParent; + }; + + if (slice.content.childCount > 1) { + return markdownSerializer.serialize(slice.content); + } else { + const targetNode = traverseToParentOfLeaf(slice.content.firstChild, slice.content, slice.openStart); + + let currentNode = targetNode; + while (currentNode && currentNode.content && currentNode.childCount === 1 && currentNode.firstChild) { + currentNode = currentNode.firstChild; + } + if (currentNode instanceof Node && currentNode.isText) { + return currentNode.text; + } + + return markdownSerializer.serialize(targetNode); + } + }, + }, + }); From e059bccfba6dd54cfec20418adf74b206a3ad0dd Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Wed, 21 May 2025 21:21:25 +0530 Subject: [PATCH 09/20] chore: refactoring out onCreate into a common utility --- .../extensions/custom-image/custom-image.ts | 17 ---------- .../src/core/extensions/image/extension.tsx | 19 ----------- .../editor/src/core/extensions/utility.ts | 8 +++++ .../editor/src/core/helpers/image-helpers.ts | 32 +++++++++++++++++++ .../editor/src/core/plugins/file/delete.ts | 6 ++-- .../editor/src/core/plugins/file/restore.ts | 6 ++-- 6 files changed, 46 insertions(+), 42 deletions(-) create mode 100644 packages/editor/src/core/helpers/image-helpers.ts 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 3c9c4d3c568..c9cabf847ec 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -97,23 +97,6 @@ export const CustomImageExtension = (props: TFileHandler) => { }; }, - onCreate(this) { - const imageSources = new Set(); - this.editor.state.doc.descendants((node) => { - if (node.type.name === this.name) { - if (!node.attrs.src?.startsWith("http")) return; - imageSources.add(node.attrs.src); - } - }); - imageSources.forEach(async (src) => { - try { - await restoreImageFn(src); - } catch (error) { - console.error("Error restoring image: ", error); - } - }); - }, - addStorage() { return { fileMap: new Map(), diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index ffcb941f930..4dc63d722fe 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -14,7 +14,6 @@ export type ImageExtensionStorage = { export const ImageExtension = (fileHandler: TFileHandler) => { const { getAssetSrc, - restore: restoreImageFn, validation: { maxFileSize }, } = fileHandler; @@ -26,24 +25,6 @@ export const ImageExtension = (fileHandler: TFileHandler) => { }; }, - onCreate(this) { - const imageSources = new Set(); - this.editor.state.doc.descendants((node) => { - if (node.type.name === this.name) { - if (!node.attrs.src?.startsWith("http")) return; - - imageSources.add(node.attrs.src); - } - }); - imageSources.forEach(async (src) => { - try { - await restoreImageFn(src); - } catch (error) { - console.error("Error restoring image: ", error); - } - }); - }, - // storage to keep track of image states Map addStorage() { return { diff --git a/packages/editor/src/core/extensions/utility.ts b/packages/editor/src/core/extensions/utility.ts index d6058272a49..1d656de5a8e 100644 --- a/packages/editor/src/core/extensions/utility.ts +++ b/packages/editor/src/core/extensions/utility.ts @@ -1,5 +1,8 @@ import { Extension } from "@tiptap/core"; +// prosemirror plugins import codemark from "prosemirror-codemark"; +// helpers +import { restorePublicImages } from "@/helpers/image-helpers"; // plugins import { DropHandlerPlugin } from "@/plugins/drop"; import { FilePlugins } from "@/plugins/file/root"; @@ -27,6 +30,7 @@ type Props = { export const UtilityExtension = (props: Props) => { const { fileHandler, isEditable } = props; + const { restore: restoreImageFn } = fileHandler; return Extension.create, UtilityExtensionStorage>({ name: "utility", @@ -45,6 +49,10 @@ export const UtilityExtension = (props: Props) => { ]; }, + onCreate() { + restorePublicImages(this.editor, restoreImageFn); + }, + addStorage() { return { assetsUploadStatus: isEditable && "assetsUploadStatus" in fileHandler ? fileHandler.assetsUploadStatus : {}, diff --git a/packages/editor/src/core/helpers/image-helpers.ts b/packages/editor/src/core/helpers/image-helpers.ts new file mode 100644 index 00000000000..9fcb877f9d6 --- /dev/null +++ b/packages/editor/src/core/helpers/image-helpers.ts @@ -0,0 +1,32 @@ +import { Editor } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; +// types +import { TFileHandler } from "@/types"; + +/** + * Finds all public image nodes in the document and restores them using the provided restore function + * + * Never remove this onCreate hook, it's a hack to restore old public + * images, since they don't give error if they've been deleted as they are + * rendered directly from image source instead of going through the + * apiserver + */ +export const restorePublicImages = (editor: Editor, restoreImageFn: TFileHandler["restore"]) => { + const imageSources = new Set(); + editor.state.doc.descendants((node) => { + if ([CORE_EXTENSIONS.IMAGE, CORE_EXTENSIONS.CUSTOM_IMAGE].includes(node.type.name as CORE_EXTENSIONS)) { + if (!node.attrs.src?.startsWith("http")) return; + + imageSources.add(node.attrs.src); + } + }); + + imageSources.forEach(async (src) => { + try { + await restoreImageFn(src); + } catch (error) { + console.error("Error restoring image: ", error); + } + }); +}; diff --git a/packages/editor/src/core/plugins/file/delete.ts b/packages/editor/src/core/plugins/file/delete.ts index 2a12794e466..3d7e16fe8e7 100644 --- a/packages/editor/src/core/plugins/file/delete.ts +++ b/packages/editor/src/core/plugins/file/delete.ts @@ -20,10 +20,10 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand const nodeType = node.type.name; const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; if (nodeFileSetDetails) { - if (newFileSources[nodeType]) { - newFileSources[nodeType].add(node.attrs.src); + if (newFileSources.nodeType) { + newFileSources.nodeType.add(node.attrs.src); } else { - newFileSources[nodeType] = new Set([node.attrs.src]); + newFileSources.nodeType = new Set([node.attrs.src]); } } }); diff --git a/packages/editor/src/core/plugins/file/restore.ts b/packages/editor/src/core/plugins/file/restore.ts index d7f36225617..791c3ce2fae 100644 --- a/packages/editor/src/core/plugins/file/restore.ts +++ b/packages/editor/src/core/plugins/file/restore.ts @@ -22,10 +22,10 @@ export const TrackFileRestorationPlugin = (editor: Editor, restoreHandler: TFile const nodeType = node.type.name; const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; if (nodeFileSetDetails) { - if (oldFileSources[nodeType]) { - oldFileSources[nodeType].add(node.attrs.src); + if (oldFileSources.nodeType) { + oldFileSources.nodeType.add(node.attrs.src); } else { - oldFileSources[nodeType] = new Set([node.attrs.src]); + oldFileSources.nodeType = new Set([node.attrs.src]); } } }); From 017f114c7351e758ae0c20afe5d1ffa6f5d8466d Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 22 May 2025 13:59:10 +0530 Subject: [PATCH 10/20] refactor: work item embed extension --- .../editors/document/collaborative-editor.tsx | 4 +- .../editors/document/read-only-editor.tsx | 4 +- .../src/core/extensions/core-without-props.ts | 14 ++-- packages/editor/src/core/extensions/index.ts | 2 +- .../src/core/extensions/issue-embed/index.ts | 2 - .../issue-embed/issue-embed-without-props.ts | 43 ------------ .../extensions/issue-embed/widget-node.tsx | 66 ------------------- .../work-item-embed/extension-config.ts | 43 ++++++++++++ .../core/extensions/work-item-embed/index.ts | 2 + .../work-item-embed/widget-node.tsx | 30 +++++++++ 10 files changed, 87 insertions(+), 123 deletions(-) delete mode 100644 packages/editor/src/core/extensions/issue-embed/index.ts delete mode 100644 packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts delete mode 100644 packages/editor/src/core/extensions/issue-embed/widget-node.tsx create mode 100644 packages/editor/src/core/extensions/work-item-embed/extension-config.ts create mode 100644 packages/editor/src/core/extensions/work-item-embed/index.ts create mode 100644 packages/editor/src/core/extensions/work-item-embed/widget-node.tsx 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 623ec9508c8..a7ff5503238 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -7,7 +7,7 @@ import { DocumentContentLoader, PageRenderer } from "@/components/editors"; // constants import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config"; // extensions -import { IssueWidget } from "@/extensions"; +import { WorkItemEmbedExtension } from "@/extensions"; // helpers import { getEditorClassNames } from "@/helpers/common"; // hooks @@ -41,7 +41,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { const extensions: Extensions = []; if (embedHandler?.issue) { extensions.push( - IssueWidget({ + WorkItemEmbedExtension({ widgetCallback: embedHandler.issue.widgetCallback, }) ); diff --git a/packages/editor/src/core/components/editors/document/read-only-editor.tsx b/packages/editor/src/core/components/editors/document/read-only-editor.tsx index 54a1f96e2c2..2d2e3083016 100644 --- a/packages/editor/src/core/components/editors/document/read-only-editor.tsx +++ b/packages/editor/src/core/components/editors/document/read-only-editor.tsx @@ -7,7 +7,7 @@ import { PageRenderer } from "@/components/editors"; // constants import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config"; // extensions -import { IssueWidget } from "@/extensions"; +import { WorkItemEmbedExtension } from "@/extensions"; // helpers import { getEditorClassNames } from "@/helpers/common"; // hooks @@ -53,7 +53,7 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => { const extensions: Extensions = []; if (embedHandler?.issue) { extensions.push( - IssueWidget({ + WorkItemEmbedExtension({ widgetCallback: embedHandler.issue.widgetCallback, }) ); diff --git a/packages/editor/src/core/extensions/core-without-props.ts b/packages/editor/src/core/extensions/core-without-props.ts index ed9f5c1a4b5..f71e8241e08 100644 --- a/packages/editor/src/core/extensions/core-without-props.ts +++ b/packages/editor/src/core/extensions/core-without-props.ts @@ -3,24 +3,24 @@ import TaskList from "@tiptap/extension-task-list"; import TextStyle from "@tiptap/extension-text-style"; import TiptapUnderline from "@tiptap/extension-underline"; import StarterKit from "@tiptap/starter-kit"; -// extensions // helpers import { isValidHttpUrl } from "@/helpers/common"; +// plane editor imports +import { CoreEditorAdditionalExtensionsWithoutProps } from "@/plane-editor/extensions/core/without-props"; +// extensions +import { CustomCalloutExtensionConfig } from "./callout/extension-config"; import { CustomCodeBlockExtensionWithoutProps } from "./code/without-props"; import { CustomCodeInlineExtension } from "./code-inline"; +import { CustomColorExtension } from "./custom-color"; import { CustomLinkExtension } from "./custom-link"; import { CustomHorizontalRule } from "./horizontal-rule"; import { ImageExtensionWithoutProps } from "./image"; import { CustomImageComponentWithoutProps } from "./image/image-component-without-props"; -import { IssueWidgetWithoutProps } from "./issue-embed/issue-embed-without-props"; import { CustomMentionExtensionConfig } from "./mentions/extension-config"; import { CustomQuoteExtension } from "./quote"; import { TableHeader, TableCell, TableRow, Table } from "./table"; import { CustomTextAlignExtension } from "./text-align"; -import { CustomCalloutExtensionConfig } from "./callout/extension-config"; -import { CustomColorExtension } from "./custom-color"; -// plane editor extensions -import { CoreEditorAdditionalExtensionsWithoutProps } from "@/plane-editor/extensions/core/without-props"; +import { WorkItemEmbedExtensionConfig } from "./work-item-embed/extension-config"; export const CoreEditorExtensionsWithoutProps = [ StarterKit.configure({ @@ -104,4 +104,4 @@ export const CoreEditorExtensionsWithoutProps = [ ...CoreEditorAdditionalExtensionsWithoutProps, ]; -export const DocumentEditorExtensionsWithoutProps = [IssueWidgetWithoutProps()]; +export const DocumentEditorExtensionsWithoutProps = [WorkItemEmbedExtensionConfig]; diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts index 4a0e9790f1b..373623f4378 100644 --- a/packages/editor/src/core/extensions/index.ts +++ b/packages/editor/src/core/extensions/index.ts @@ -5,11 +5,11 @@ export * from "./custom-image"; export * from "./custom-link"; export * from "./custom-list-keymap"; export * from "./image"; -export * from "./issue-embed"; export * from "./mentions"; export * from "./slash-commands"; export * from "./table"; export * from "./typography"; +export * from "./work-item-embed"; export * from "./core-without-props"; export * from "./custom-color"; export * from "./enter-key-extension"; diff --git a/packages/editor/src/core/extensions/issue-embed/index.ts b/packages/editor/src/core/extensions/issue-embed/index.ts deleted file mode 100644 index f47619a0342..00000000000 --- a/packages/editor/src/core/extensions/issue-embed/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./widget-node"; -export * from "./issue-embed-without-props"; diff --git a/packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts b/packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts deleted file mode 100644 index db7c0c8c1b8..00000000000 --- a/packages/editor/src/core/extensions/issue-embed/issue-embed-without-props.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { mergeAttributes, Node } from "@tiptap/core"; -// constants -import { CORE_EXTENSIONS } from "@/constants/extension"; - -export const IssueWidgetWithoutProps = () => - Node.create({ - name: CORE_EXTENSIONS.WORK_ITEM_EMBED, - group: "block", - atom: true, - selectable: true, - draggable: true, - - addAttributes() { - return { - entity_identifier: { - default: undefined, - }, - project_identifier: { - default: undefined, - }, - workspace_identifier: { - default: undefined, - }, - id: { - default: undefined, - }, - entity_name: { - default: undefined, - }, - }; - }, - - parseHTML() { - return [ - { - tag: "issue-embed-component", - }, - ]; - }, - renderHTML({ HTMLAttributes }) { - return ["issue-embed-component", mergeAttributes(HTMLAttributes)]; - }, - }); diff --git a/packages/editor/src/core/extensions/issue-embed/widget-node.tsx b/packages/editor/src/core/extensions/issue-embed/widget-node.tsx deleted file mode 100644 index a216ab6d92f..00000000000 --- a/packages/editor/src/core/extensions/issue-embed/widget-node.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { mergeAttributes, Node } from "@tiptap/core"; -import { ReactNodeViewRenderer, NodeViewWrapper } from "@tiptap/react"; - -type Props = { - widgetCallback: ({ - issueId, - projectId, - workspaceSlug, - }: { - issueId: string; - projectId: string | undefined; - workspaceSlug: string | undefined; - }) => React.ReactNode; -}; - -export const IssueWidget = (props: Props) => - Node.create({ - name: "issue-embed-component", - group: "block", - atom: true, - selectable: true, - draggable: true, - - addAttributes() { - return { - entity_identifier: { - default: undefined, - }, - project_identifier: { - default: undefined, - }, - workspace_identifier: { - default: undefined, - }, - id: { - default: undefined, - }, - entity_name: { - default: undefined, - }, - }; - }, - - addNodeView() { - return ReactNodeViewRenderer((issueProps: any) => ( - - {props.widgetCallback({ - issueId: issueProps.node.attrs.entity_identifier, - projectId: issueProps.node.attrs.project_identifier, - workspaceSlug: issueProps.node.attrs.workspace_identifier, - })} - - )); - }, - - parseHTML() { - return [ - { - tag: "issue-embed-component", - }, - ]; - }, - renderHTML({ HTMLAttributes }) { - return ["issue-embed-component", mergeAttributes(HTMLAttributes)]; - }, - }); diff --git a/packages/editor/src/core/extensions/work-item-embed/extension-config.ts b/packages/editor/src/core/extensions/work-item-embed/extension-config.ts new file mode 100644 index 00000000000..0ea25c770d5 --- /dev/null +++ b/packages/editor/src/core/extensions/work-item-embed/extension-config.ts @@ -0,0 +1,43 @@ +import { mergeAttributes, Node } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; + +export const WorkItemEmbedExtensionConfig = Node.create({ + name: CORE_EXTENSIONS.WORK_ITEM_EMBED, + group: "block", + atom: true, + selectable: true, + draggable: true, + + addAttributes() { + return { + entity_identifier: { + default: undefined, + }, + project_identifier: { + default: undefined, + }, + workspace_identifier: { + default: undefined, + }, + id: { + default: undefined, + }, + entity_name: { + default: undefined, + }, + }; + }, + + parseHTML() { + return [ + { + tag: "issue-embed-component", + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ["issue-embed-component", mergeAttributes(HTMLAttributes)]; + }, +}); diff --git a/packages/editor/src/core/extensions/work-item-embed/index.ts b/packages/editor/src/core/extensions/work-item-embed/index.ts new file mode 100644 index 00000000000..459bafe024c --- /dev/null +++ b/packages/editor/src/core/extensions/work-item-embed/index.ts @@ -0,0 +1,2 @@ +export * from "./widget-node"; +export * from "./extension-config"; diff --git a/packages/editor/src/core/extensions/work-item-embed/widget-node.tsx b/packages/editor/src/core/extensions/work-item-embed/widget-node.tsx new file mode 100644 index 00000000000..64e655a4088 --- /dev/null +++ b/packages/editor/src/core/extensions/work-item-embed/widget-node.tsx @@ -0,0 +1,30 @@ +import { ReactNodeViewRenderer, NodeViewWrapper } from "@tiptap/react"; +// local imports +import { WorkItemEmbedExtensionConfig } from "./extension-config"; + +type Props = { + widgetCallback: ({ + issueId, + projectId, + workspaceSlug, + }: { + issueId: string; + projectId: string | undefined; + workspaceSlug: string | undefined; + }) => React.ReactNode; +}; + +export const WorkItemEmbedExtension = (props: Props) => + WorkItemEmbedExtensionConfig.extend({ + addNodeView() { + return ReactNodeViewRenderer((issueProps: any) => ( + + {props.widgetCallback({ + issueId: issueProps.node.attrs.entity_identifier, + projectId: issueProps.node.attrs.project_identifier, + workspaceSlug: issueProps.node.attrs.workspace_identifier, + })} + + )); + }, + }); From 90c51c793c848c23525cb7dd353da83ef6c9e0c8 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 22 May 2025 15:09:28 +0530 Subject: [PATCH 11/20] chore: use extension enums --- .../components/editors/editor-container.tsx | 4 +- .../src/core/components/menus/block-menu.tsx | 9 ++-- .../menus/bubble-menu/link-selector.tsx | 6 ++- .../components/menus/bubble-menu/root.tsx | 5 ++- .../src/core/components/menus/menu-items.ts | 44 ++++++++++--------- .../src/core/extensions/code/code-block.ts | 4 +- .../components/image-uploader.tsx | 3 +- .../core/extensions/custom-link/extension.tsx | 2 +- .../custom-link/helpers/clickHandler.ts | 2 +- .../custom-list-keymap/list-helpers.ts | 16 ++++--- .../custom-list-keymap/list-keymap.ts | 12 ++--- .../core/extensions/enter-key-extension.tsx | 4 +- .../editor/src/core/extensions/extensions.tsx | 10 ++--- .../editor/src/core/extensions/keymap.tsx | 8 ++-- .../extensions/mentions/mention-node-view.tsx | 2 +- packages/editor/src/core/extensions/quote.tsx | 4 +- .../core/extensions/slash-commands/root.tsx | 4 +- .../extensions/table/table/table-controls.ts | 16 +++---- .../extensions/table/table/table-view.tsx | 5 ++- .../src/core/extensions/table/table/table.ts | 4 +- .../delete-table-when-all-cells-selected.ts | 11 +++-- .../insert-line-above-table-action.ts | 8 ++-- .../insert-line-below-table-action.ts | 10 +++-- .../{widget-node.tsx => extension.tsx} | 0 .../core/extensions/work-item-embed/index.ts | 3 +- packages/editor/src/core/helpers/common.ts | 6 ++- .../src/core/helpers/editor-commands.ts | 20 +++++---- ...insert-empty-paragraph-at-node-boundary.ts | 10 +++-- packages/editor/src/core/hooks/use-editor.ts | 2 +- .../editor/src/core/plugins/drag-handle.ts | 31 ++++++++----- .../src/core/plugins/markdown-clipboard.ts | 6 ++- 31 files changed, 159 insertions(+), 112 deletions(-) rename packages/editor/src/core/extensions/work-item-embed/{widget-node.tsx => extension.tsx} (100%) diff --git a/packages/editor/src/core/components/editors/editor-container.tsx b/packages/editor/src/core/components/editors/editor-container.tsx index 8a069d612a7..6daa0719a0f 100644 --- a/packages/editor/src/core/components/editors/editor-container.tsx +++ b/packages/editor/src/core/components/editors/editor-container.tsx @@ -54,10 +54,10 @@ export const EditorContainer: FC = (props) => { const lastNode = lastNodePos.node(); // Check if the last node is a not paragraph - if (lastNode && lastNode.type.name !== "paragraph") { + if (lastNode && lastNode.type.name !== CORE_EXTENSIONS.PARAGRAPH) { // If last node is not a paragraph, insert a new paragraph at the end const endPosition = editor?.state.doc.content.size; - editor?.chain().insertContentAt(endPosition, { type: "paragraph" }).run(); + editor?.chain().insertContentAt(endPosition, { type: CORE_EXTENSIONS.PARAGRAPH }).run(); // Focus the newly added paragraph for immediate editing editor diff --git a/packages/editor/src/core/components/menus/block-menu.tsx b/packages/editor/src/core/components/menus/block-menu.tsx index c143abd009c..bd86628cb32 100644 --- a/packages/editor/src/core/components/menus/block-menu.tsx +++ b/packages/editor/src/core/components/menus/block-menu.tsx @@ -1,7 +1,9 @@ -import { useCallback, useEffect, useRef } from "react"; import { Editor } from "@tiptap/react"; -import tippy, { Instance } from "tippy.js"; import { Copy, LucideIcon, Trash2 } from "lucide-react"; +import { useCallback, useEffect, useRef } from "react"; +import tippy, { Instance } from "tippy.js"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; interface BlockMenuProps { editor: Editor; @@ -102,7 +104,8 @@ export const BlockMenu = (props: BlockMenuProps) => { key: "duplicate", label: "Duplicate", isDisabled: - editor.state.selection.content().content.firstChild?.type.name === "image" || editor.isActive("imageComponent"), + editor.state.selection.content().content.firstChild?.type.name === CORE_EXTENSIONS.IMAGE || + editor.isActive(CORE_EXTENSIONS.CUSTOM_IMAGE), onClick: (e) => { e.preventDefault(); e.stopPropagation(); diff --git a/packages/editor/src/core/components/menus/bubble-menu/link-selector.tsx b/packages/editor/src/core/components/menus/bubble-menu/link-selector.tsx index 1dd47c5bb33..6f582f89c67 100644 --- a/packages/editor/src/core/components/menus/bubble-menu/link-selector.tsx +++ b/packages/editor/src/core/components/menus/bubble-menu/link-selector.tsx @@ -1,8 +1,10 @@ import { Editor } from "@tiptap/core"; import { Check, Link, Trash2 } from "lucide-react"; import { Dispatch, FC, SetStateAction, useCallback, useRef, useState } from "react"; -// plane utils +// plane imports import { cn } from "@plane/utils"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // helpers import { isValidHttpUrl } from "@/helpers/common"; import { setLinkEditor, unsetLinkEditor } from "@/helpers/editor-commands"; @@ -43,7 +45,7 @@ export const BubbleMenuLinkSelector: FC = (props) => { "h-full flex items-center gap-1 px-3 text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 active:bg-custom-background-80 rounded transition-colors", { "bg-custom-background-80": isOpen, - "text-custom-text-100": editor.isActive("link"), + "text-custom-text-100": editor.isActive(CORE_EXTENSIONS.CUSTOM_LINK), } )} onClick={(e) => { diff --git a/packages/editor/src/core/components/menus/bubble-menu/root.tsx b/packages/editor/src/core/components/menus/bubble-menu/root.tsx index 02eb8d48675..30a7c5620b5 100644 --- a/packages/editor/src/core/components/menus/bubble-menu/root.tsx +++ b/packages/editor/src/core/components/menus/bubble-menu/root.tsx @@ -18,6 +18,7 @@ import { } from "@/components/menus"; // constants import { COLORS_LIST } from "@/constants/common"; +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { isCellSelection } from "@/extensions/table/table/utilities/is-cell-selection"; // local components @@ -90,8 +91,8 @@ export const EditorBubbleMenu: FC = (props: { editor: Edi if ( empty || !editor.isEditable || - editor.isActive("image") || - editor.isActive("imageComponent") || + editor.isActive(CORE_EXTENSIONS.IMAGE) || + editor.isActive(CORE_EXTENSIONS.CUSTOM_IMAGE) || isNodeSelection(selection) || isCellSelection(selection) || isSelecting diff --git a/packages/editor/src/core/components/menus/menu-items.ts b/packages/editor/src/core/components/menus/menu-items.ts index 4268ccb6c48..974251559e8 100644 --- a/packages/editor/src/core/components/menus/menu-items.ts +++ b/packages/editor/src/core/components/menus/menu-items.ts @@ -23,6 +23,8 @@ import { Palette, AlignCenter, } from "lucide-react"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // helpers import { insertHorizontalRule, @@ -65,7 +67,7 @@ export type EditorMenuItem = { export const TextItem = (editor: Editor): EditorMenuItem<"text"> => ({ key: "text", name: "Text", - isActive: () => editor.isActive("paragraph"), + isActive: () => editor.isActive(CORE_EXTENSIONS.PARAGRAPH), command: () => setText(editor), icon: CaseSensitive, }); @@ -73,7 +75,7 @@ export const TextItem = (editor: Editor): EditorMenuItem<"text"> => ({ export const HeadingOneItem = (editor: Editor): EditorMenuItem<"h1"> => ({ key: "h1", name: "Heading 1", - isActive: () => editor.isActive("heading", { level: 1 }), + isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 1 }), command: () => toggleHeadingOne(editor), icon: Heading1, }); @@ -81,7 +83,7 @@ export const HeadingOneItem = (editor: Editor): EditorMenuItem<"h1"> => ({ export const HeadingTwoItem = (editor: Editor): EditorMenuItem<"h2"> => ({ key: "h2", name: "Heading 2", - isActive: () => editor.isActive("heading", { level: 2 }), + isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 2 }), command: () => toggleHeadingTwo(editor), icon: Heading2, }); @@ -89,7 +91,7 @@ export const HeadingTwoItem = (editor: Editor): EditorMenuItem<"h2"> => ({ export const HeadingThreeItem = (editor: Editor): EditorMenuItem<"h3"> => ({ key: "h3", name: "Heading 3", - isActive: () => editor.isActive("heading", { level: 3 }), + isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 3 }), command: () => toggleHeadingThree(editor), icon: Heading3, }); @@ -97,7 +99,7 @@ export const HeadingThreeItem = (editor: Editor): EditorMenuItem<"h3"> => ({ export const HeadingFourItem = (editor: Editor): EditorMenuItem<"h4"> => ({ key: "h4", name: "Heading 4", - isActive: () => editor.isActive("heading", { level: 4 }), + isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 4 }), command: () => toggleHeadingFour(editor), icon: Heading4, }); @@ -105,7 +107,7 @@ export const HeadingFourItem = (editor: Editor): EditorMenuItem<"h4"> => ({ export const HeadingFiveItem = (editor: Editor): EditorMenuItem<"h5"> => ({ key: "h5", name: "Heading 5", - isActive: () => editor.isActive("heading", { level: 5 }), + isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 5 }), command: () => toggleHeadingFive(editor), icon: Heading5, }); @@ -113,7 +115,7 @@ export const HeadingFiveItem = (editor: Editor): EditorMenuItem<"h5"> => ({ export const HeadingSixItem = (editor: Editor): EditorMenuItem<"h6"> => ({ key: "h6", name: "Heading 6", - isActive: () => editor.isActive("heading", { level: 6 }), + isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 6 }), command: () => toggleHeadingSix(editor), icon: Heading6, }); @@ -121,7 +123,7 @@ export const HeadingSixItem = (editor: Editor): EditorMenuItem<"h6"> => ({ export const BoldItem = (editor: Editor): EditorMenuItem<"bold"> => ({ key: "bold", name: "Bold", - isActive: () => editor?.isActive("bold"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.BOLD), command: () => toggleBold(editor), icon: BoldIcon, }); @@ -129,7 +131,7 @@ export const BoldItem = (editor: Editor): EditorMenuItem<"bold"> => ({ export const ItalicItem = (editor: Editor): EditorMenuItem<"italic"> => ({ key: "italic", name: "Italic", - isActive: () => editor?.isActive("italic"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.ITALIC), command: () => toggleItalic(editor), icon: ItalicIcon, }); @@ -137,7 +139,7 @@ export const ItalicItem = (editor: Editor): EditorMenuItem<"italic"> => ({ export const UnderLineItem = (editor: Editor): EditorMenuItem<"underline"> => ({ key: "underline", name: "Underline", - isActive: () => editor?.isActive("underline"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.UNDERLINE), command: () => toggleUnderline(editor), icon: UnderlineIcon, }); @@ -145,7 +147,7 @@ export const UnderLineItem = (editor: Editor): EditorMenuItem<"underline"> => ({ export const StrikeThroughItem = (editor: Editor): EditorMenuItem<"strikethrough"> => ({ key: "strikethrough", name: "Strikethrough", - isActive: () => editor?.isActive("strike"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.STRIKETHROUGH), command: () => toggleStrike(editor), icon: StrikethroughIcon, }); @@ -153,7 +155,7 @@ export const StrikeThroughItem = (editor: Editor): EditorMenuItem<"strikethrough export const BulletListItem = (editor: Editor): EditorMenuItem<"bulleted-list"> => ({ key: "bulleted-list", name: "Bulleted list", - isActive: () => editor?.isActive("bulletList"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.BULLET_LIST), command: () => toggleBulletList(editor), icon: ListIcon, }); @@ -161,7 +163,7 @@ export const BulletListItem = (editor: Editor): EditorMenuItem<"bulleted-list"> export const NumberedListItem = (editor: Editor): EditorMenuItem<"numbered-list"> => ({ key: "numbered-list", name: "Numbered list", - isActive: () => editor?.isActive("orderedList"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.ORDERED_LIST), command: () => toggleOrderedList(editor), icon: ListOrderedIcon, }); @@ -169,7 +171,7 @@ export const NumberedListItem = (editor: Editor): EditorMenuItem<"numbered-list" export const TodoListItem = (editor: Editor): EditorMenuItem<"to-do-list"> => ({ key: "to-do-list", name: "To-do list", - isActive: () => editor.isActive("taskItem"), + isActive: () => editor.isActive(CORE_EXTENSIONS.TASK_ITEM), command: () => toggleTaskList(editor), icon: CheckSquare, }); @@ -177,7 +179,7 @@ export const TodoListItem = (editor: Editor): EditorMenuItem<"to-do-list"> => ({ export const QuoteItem = (editor: Editor): EditorMenuItem<"quote"> => ({ key: "quote", name: "Quote", - isActive: () => editor?.isActive("blockquote"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.BLOCKQUOTE), command: () => toggleBlockquote(editor), icon: TextQuote, }); @@ -185,7 +187,7 @@ export const QuoteItem = (editor: Editor): EditorMenuItem<"quote"> => ({ export const CodeItem = (editor: Editor): EditorMenuItem<"code"> => ({ key: "code", name: "Code", - isActive: () => editor?.isActive("code") || editor?.isActive("codeBlock"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.CODE_INLINE) || editor?.isActive(CORE_EXTENSIONS.CODE_BLOCK), command: () => toggleCodeBlock(editor), icon: CodeIcon, }); @@ -193,7 +195,7 @@ export const CodeItem = (editor: Editor): EditorMenuItem<"code"> => ({ export const TableItem = (editor: Editor): EditorMenuItem<"table"> => ({ key: "table", name: "Table", - isActive: () => editor?.isActive("table"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.TABLE), command: () => insertTableCommand(editor), icon: TableIcon, }); @@ -201,7 +203,7 @@ export const TableItem = (editor: Editor): EditorMenuItem<"table"> => ({ export const ImageItem = (editor: Editor): EditorMenuItem<"image"> => ({ key: "image", name: "Image", - isActive: () => editor?.isActive("image") || editor?.isActive("imageComponent"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.IMAGE) || editor?.isActive(CORE_EXTENSIONS.CUSTOM_IMAGE), command: () => insertImage({ editor, event: "insert", pos: editor.state.selection.from }), icon: ImageIcon, }); @@ -210,7 +212,7 @@ export const HorizontalRuleItem = (editor: Editor) => ({ key: "divider", name: "Divider", - isActive: () => editor?.isActive("horizontalRule"), + isActive: () => editor?.isActive(CORE_EXTENSIONS.HORIZONTAL_RULE), command: () => insertHorizontalRule(editor), icon: MinusSquare, }) as const; @@ -218,7 +220,7 @@ export const HorizontalRuleItem = (editor: Editor) => export const TextColorItem = (editor: Editor): EditorMenuItem<"text-color"> => ({ key: "text-color", name: "Color", - isActive: (props) => editor.isActive("customColor", { color: props?.color }), + isActive: (props) => editor.isActive(CORE_EXTENSIONS.CUSTOM_COLOR, { color: props?.color }), command: (props) => { if (!props) return; toggleTextColor(props.color, editor); @@ -229,7 +231,7 @@ export const TextColorItem = (editor: Editor): EditorMenuItem<"text-color"> => ( export const BackgroundColorItem = (editor: Editor): EditorMenuItem<"background-color"> => ({ key: "background-color", name: "Background color", - isActive: (props) => editor.isActive("customColor", { backgroundColor: props?.color }), + isActive: (props) => editor.isActive(CORE_EXTENSIONS.CUSTOM_COLOR, { backgroundColor: props?.color }), command: (props) => { if (!props) return; toggleBackgroundColor(props.color, editor); diff --git a/packages/editor/src/core/extensions/code/code-block.ts b/packages/editor/src/core/extensions/code/code-block.ts index 8bc0c8d2d27..c246d45fbca 100644 --- a/packages/editor/src/core/extensions/code/code-block.ts +++ b/packages/editor/src/core/extensions/code/code-block.ts @@ -120,7 +120,7 @@ export const CodeBlock = Node.create({ toggleCodeBlock: (attributes) => ({ commands }) => - commands.toggleNode(this.name, "paragraph", attributes), + commands.toggleNode(this.name, CORE_EXTENSIONS.PARAGRAPH, attributes), }; }, @@ -261,7 +261,7 @@ export const CodeBlock = Node.create({ return false; } - if (this.editor.isActive("code")) { + if (this.editor.isActive(CORE_EXTENSIONS.CODE_INLINE)) { // Check if it's an inline code block event.preventDefault(); const text = event.clipboardData.getData("text/plain"); 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 ccecf16181b..c7fe60c8567 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 @@ -4,6 +4,7 @@ import { ChangeEvent, useCallback, useEffect, useMemo, useRef } from "react"; import { cn } from "@plane/utils"; // constants import { ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config"; +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { CustoBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image"; // hooks @@ -57,7 +58,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { // control cursor position after upload const nextNode = editor.state.doc.nodeAt(pos + 1); - if (nextNode && nextNode.type.name === "paragraph") { + if (nextNode && nextNode.type.name === CORE_EXTENSIONS.PARAGRAPH) { // If there is a paragraph node after the image component, move the focus to the next node editor.commands.setTextSelection(pos + 1); } else { diff --git a/packages/editor/src/core/extensions/custom-link/extension.tsx b/packages/editor/src/core/extensions/custom-link/extension.tsx index 9cc6462d6e4..182afc9f8fe 100644 --- a/packages/editor/src/core/extensions/custom-link/extension.tsx +++ b/packages/editor/src/core/extensions/custom-link/extension.tsx @@ -49,7 +49,7 @@ export interface LinkOptions { declare module "@tiptap/core" { interface Commands { - link: { + [CORE_EXTENSIONS.CUSTOM_LINK]: { /** * Set a link mark */ diff --git a/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts b/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts index 1b084d1ac52..72906bc9424 100644 --- a/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts +++ b/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts @@ -16,7 +16,7 @@ export function clickHandler(options: ClickHandlerOptions): Plugin { } let a = event.target as HTMLElement; - const els = []; + const els: HTMLElement[] = []; while (a?.nodeName !== "DIV") { els.push(a); diff --git a/packages/editor/src/core/extensions/custom-list-keymap/list-helpers.ts b/packages/editor/src/core/extensions/custom-list-keymap/list-helpers.ts index 7d4cad17e00..547f9f17e10 100644 --- a/packages/editor/src/core/extensions/custom-list-keymap/list-helpers.ts +++ b/packages/editor/src/core/extensions/custom-list-keymap/list-helpers.ts @@ -1,12 +1,14 @@ import { Editor, getNodeType, getNodeAtPosition, isAtEndOfNode, isAtStartOfNode, isNodeActive } from "@tiptap/core"; import { Node, NodeType } from "@tiptap/pm/model"; import { EditorState } from "@tiptap/pm/state"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => { const { $from } = state.selection; const nodeType = getNodeType(typeOrName, state.schema); - let currentNode = null; + let currentNode: Node | null = null; let currentDepth = $from.depth; let currentPos = $from.pos; let targetDepth: number | null = null; @@ -72,7 +74,11 @@ const getPrevListDepth = (typeOrName: string, state: EditorState) => { // Traverse up the document structure from the adjusted position for (let d = resolvedPos.depth; d > 0; d--) { const node = resolvedPos.node(d); - if (node.type.name === "bulletList" || node.type.name === "orderedList" || node.type.name === "taskList") { + if ( + [CORE_EXTENSIONS.BULLET_LIST, CORE_EXTENSIONS.ORDERED_LIST, CORE_EXTENSIONS.TASK_LIST].includes( + node.type.name as CORE_EXTENSIONS + ) + ) { // Increment depth for each list ancestor found depth++; } @@ -309,12 +315,12 @@ const isCurrentParagraphASibling = (state: EditorState): boolean => { // Ensure we're in a paragraph and the parent is a list item. if ( - currentParagraphNode.type.name === "paragraph" && - (listItemNode.type.name === "listItem" || listItemNode.type.name === "taskItem") + currentParagraphNode.type.name === CORE_EXTENSIONS.PARAGRAPH && + [CORE_EXTENSIONS.LIST_ITEM, CORE_EXTENSIONS.TASK_ITEM].includes(listItemNode.type.name as CORE_EXTENSIONS) ) { let paragraphNodesCount = 0; listItemNode.forEach((child) => { - if (child.type.name === "paragraph") { + if (child.type.name === CORE_EXTENSIONS.PARAGRAPH) { paragraphNodesCount++; } }); diff --git a/packages/editor/src/core/extensions/custom-list-keymap/list-keymap.ts b/packages/editor/src/core/extensions/custom-list-keymap/list-keymap.ts index 2a17838fd8c..576888f55a5 100644 --- a/packages/editor/src/core/extensions/custom-list-keymap/list-keymap.ts +++ b/packages/editor/src/core/extensions/custom-list-keymap/list-keymap.ts @@ -1,4 +1,6 @@ import { Extension } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { handleBackspace, handleDelete } from "@/extensions/custom-list-keymap/list-helpers"; @@ -31,10 +33,10 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) => addKeyboardShortcuts() { return { Tab: () => { - if (this.editor.isActive("listItem") || this.editor.isActive("taskItem")) { - if (this.editor.commands.sinkListItem("listItem")) { + if (this.editor.isActive(CORE_EXTENSIONS.LIST_ITEM) || this.editor.isActive(CORE_EXTENSIONS.TASK_ITEM)) { + if (this.editor.commands.sinkListItem(CORE_EXTENSIONS.LIST_ITEM)) { return true; - } else if (this.editor.commands.sinkListItem("taskItem")) { + } else if (this.editor.commands.sinkListItem(CORE_EXTENSIONS.TASK_ITEM)) { return true; } return true; @@ -46,9 +48,9 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) => return true; }, "Shift-Tab": () => { - if (this.editor.commands.liftListItem("listItem")) { + if (this.editor.commands.liftListItem(CORE_EXTENSIONS.LIST_ITEM)) { return true; - } else if (this.editor.commands.liftListItem("taskItem")) { + } else if (this.editor.commands.liftListItem(CORE_EXTENSIONS.TASK_ITEM)) { return true; } // if tabIndex is set, we don't want to handle Tab key diff --git a/packages/editor/src/core/extensions/enter-key-extension.tsx b/packages/editor/src/core/extensions/enter-key-extension.tsx index 6e8014c84e5..65119425fc1 100644 --- a/packages/editor/src/core/extensions/enter-key-extension.tsx +++ b/packages/editor/src/core/extensions/enter-key-extension.tsx @@ -21,8 +21,8 @@ export const EnterKeyExtension = (onEnterKeyPress?: () => void) => "Shift-Enter": ({ editor }) => editor.commands.first(({ commands }) => [ () => commands.newlineInCode(), - () => commands.splitListItem("listItem"), - () => commands.splitListItem("taskItem"), + () => commands.splitListItem(CORE_EXTENSIONS.LIST_ITEM), + () => commands.splitListItem(CORE_EXTENSIONS.TASK_ITEM), () => commands.createParagraphNear(), () => commands.liftEmptyBlock(), () => commands.splitBlock(), diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index e9c32912716..a50a1fd1b6f 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -143,16 +143,16 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { placeholder: ({ editor, node }) => { if (!editor.isEditable) return ""; - if (node.type.name === "heading") return `Heading ${node.attrs.level}`; + if (node.type.name === CORE_EXTENSIONS.HEADING) return `Heading ${node.attrs.level}`; const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress; if (isUploadInProgress) return ""; const shouldHidePlaceholder = - editor.isActive("table") || - editor.isActive("codeBlock") || - editor.isActive("image") || - editor.isActive("imageComponent"); + editor.isActive(CORE_EXTENSIONS.TABLE) || + editor.isActive(CORE_EXTENSIONS.CODE_BLOCK) || + editor.isActive(CORE_EXTENSIONS.IMAGE) || + editor.isActive(CORE_EXTENSIONS.CUSTOM_IMAGE); if (shouldHidePlaceholder) return ""; diff --git a/packages/editor/src/core/extensions/keymap.tsx b/packages/editor/src/core/extensions/keymap.tsx index 81d60e34f67..783d7598d23 100644 --- a/packages/editor/src/core/extensions/keymap.tsx +++ b/packages/editor/src/core/extensions/keymap.tsx @@ -2,6 +2,8 @@ import { Extension } from "@tiptap/core"; import { NodeType } from "@tiptap/pm/model"; import { Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; import { canJoin } from "@tiptap/pm/transform"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; declare module "@tiptap/core" { // eslint-disable-next-line no-unused-vars @@ -87,9 +89,9 @@ export const CustomKeymap = Extension.create({ const newTr = newState.tr; const joinableNodes = [ - newState.schema.nodes["orderedList"], - newState.schema.nodes["taskList"], - newState.schema.nodes["bulletList"], + newState.schema.nodes[CORE_EXTENSIONS.ORDERED_LIST], + newState.schema.nodes[CORE_EXTENSIONS.TASK_LIST], + newState.schema.nodes[CORE_EXTENSIONS.BULLET_LIST], ]; let joined = false; 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 006336fbb67..aac00de884a 100644 --- a/packages/editor/src/core/extensions/mentions/mention-node-view.tsx +++ b/packages/editor/src/core/extensions/mentions/mention-node-view.tsx @@ -18,7 +18,7 @@ export const MentionNodeView = (props: Props) => { return ( {(extension.options as TMentionExtensionOptions).renderComponent({ - entity_identifier: attrs[EMentionComponentAttributeNames.ENTITY_IDENTIFIER], + entity_identifier: attrs[EMentionComponentAttributeNames.ENTITY_IDENTIFIER] ?? "", entity_name: attrs[EMentionComponentAttributeNames.ENTITY_NAME] ?? "user_mention", })} diff --git a/packages/editor/src/core/extensions/quote.tsx b/packages/editor/src/core/extensions/quote.tsx index 4ae81ffe4f9..99a6c10f05b 100644 --- a/packages/editor/src/core/extensions/quote.tsx +++ b/packages/editor/src/core/extensions/quote.tsx @@ -1,4 +1,6 @@ import Blockquote from "@tiptap/extension-blockquote"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export const CustomQuoteExtension = Blockquote.extend({ addKeyboardShortcuts() { @@ -10,7 +12,7 @@ export const CustomQuoteExtension = Blockquote.extend({ if (!parent) return false; - if (parent.type.name !== "blockquote") { + if (parent.type.name !== CORE_EXTENSIONS.BLOCKQUOTE) { return false; } if ($from.pos !== $to.pos) return false; diff --git a/packages/editor/src/core/extensions/slash-commands/root.tsx b/packages/editor/src/core/extensions/slash-commands/root.tsx index 98f5e8ab65c..a03912e50f0 100644 --- a/packages/editor/src/core/extensions/slash-commands/root.tsx +++ b/packages/editor/src/core/extensions/slash-commands/root.tsx @@ -36,11 +36,11 @@ const Command = Extension.create({ const parentNode = selection.$from.node(selection.$from.depth); const blockType = parentNode.type.name; - if (blockType === "codeBlock") { + if (blockType === CORE_EXTENSIONS.CODE_BLOCK) { return false; } - if (editor.isActive("table")) { + if (editor.isActive(CORE_EXTENSIONS.TABLE)) { return false; } diff --git a/packages/editor/src/core/extensions/table/table/table-controls.ts b/packages/editor/src/core/extensions/table/table/table-controls.ts index 05292257960..086dc8b8e8a 100644 --- a/packages/editor/src/core/extensions/table/table/table-controls.ts +++ b/packages/editor/src/core/extensions/table/table/table-controls.ts @@ -1,6 +1,8 @@ import { findParentNode } from "@tiptap/core"; import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state"; import { DecorationSet, Decoration } from "@tiptap/pm/view"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; const key = new PluginKey("tableControls"); @@ -17,16 +19,14 @@ export function tableControls() { }, props: { handleTripleClickOn(view, pos, node, nodePos, event, direct) { - if (node.type.name === 'tableCell') { + if (node.type.name === CORE_EXTENSIONS.TABLE_CELL) { event.preventDefault(); const $pos = view.state.doc.resolve(pos); const line = $pos.parent; const linePos = $pos.start(); const start = linePos; const end = linePos + line.nodeSize - 1; - const tr = view.state.tr.setSelection( - TextSelection.create(view.state.doc, start, end) - ); + const tr = view.state.tr.setSelection(TextSelection.create(view.state.doc, start, end)); view.dispatch(tr); return true; } @@ -52,12 +52,12 @@ export function tableControls() { if (!pos || pos.pos < 0 || pos.pos > view.state.doc.content.size) return; - const table = findParentNode((node) => node.type.name === "table")( - TextSelection.create(view.state.doc, pos.pos) - ); - const cell = findParentNode((node) => node.type.name === "tableCell" || node.type.name === "tableHeader")( + const table = findParentNode((node) => node.type.name === CORE_EXTENSIONS.TABLE)( TextSelection.create(view.state.doc, pos.pos) ); + const cell = findParentNode((node) => + [CORE_EXTENSIONS.TABLE_CELL, CORE_EXTENSIONS.TABLE_HEADER].includes(node.type.name as CORE_EXTENSIONS) + )(TextSelection.create(view.state.doc, pos.pos)); if (!table || !cell) return; diff --git a/packages/editor/src/core/extensions/table/table/table-view.tsx b/packages/editor/src/core/extensions/table/table/table-view.tsx index 2a480212673..801dd0b3fb9 100644 --- a/packages/editor/src/core/extensions/table/table/table-view.tsx +++ b/packages/editor/src/core/extensions/table/table/table-view.tsx @@ -7,6 +7,7 @@ import { Editor } from "@tiptap/core"; import { CellSelection, TableMap, updateColumnsOnResize } from "@tiptap/pm/tables"; import { icons } from "src/core/extensions/table/table/icons"; +import { CORE_EXTENSIONS } from "@/constants/extension"; type ToolboxItem = { label: string; @@ -104,12 +105,12 @@ function setTableRowBackgroundColor(editor: Editor, color: { backgroundColor: st // Find the depth of the table row node let rowDepth = hoveredCell.depth; - while (rowDepth > 0 && hoveredCell.node(rowDepth).type.name !== "tableRow") { + while (rowDepth > 0 && hoveredCell.node(rowDepth).type.name !== CORE_EXTENSIONS.TABLE_ROW) { rowDepth--; } // If we couldn't find a tableRow node, we can't set the background color - if (hoveredCell.node(rowDepth).type.name !== "tableRow") { + if (hoveredCell.node(rowDepth).type.name !== CORE_EXTENSIONS.TABLE_ROW) { return false; } diff --git a/packages/editor/src/core/extensions/table/table/table.ts b/packages/editor/src/core/extensions/table/table/table.ts index 22b6232efae..923f92123b9 100644 --- a/packages/editor/src/core/extensions/table/table/table.ts +++ b/packages/editor/src/core/extensions/table/table/table.ts @@ -221,8 +221,8 @@ export const Table = Node.create({ addKeyboardShortcuts() { return { Tab: () => { - if (this.editor.isActive("table")) { - if (this.editor.isActive("listItem") || this.editor.isActive("taskItem")) { + if (this.editor.isActive(CORE_EXTENSIONS.TABLE)) { + if (this.editor.isActive(CORE_EXTENSIONS.LIST_ITEM) || this.editor.isActive(CORE_EXTENSIONS.TASK_ITEM)) { return false; } if (this.editor.commands.goToNextCell()) { diff --git a/packages/editor/src/core/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts b/packages/editor/src/core/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts index 53388fbf238..5c84b8617da 100644 --- a/packages/editor/src/core/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts +++ b/packages/editor/src/core/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts @@ -1,4 +1,6 @@ import { findParentNodeClosestToPos, KeyboardShortcutCommand } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { isCellSelection } from "@/extensions/table/table/utilities/is-cell-selection"; @@ -10,14 +12,17 @@ export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({ edito } let cellCount = 0; - const table = findParentNodeClosestToPos(selection.ranges[0].$from, (node) => node.type.name === "table"); + const table = findParentNodeClosestToPos( + selection.ranges[0].$from, + (node) => node.type.name === CORE_EXTENSIONS.TABLE + ); table?.node.descendants((node) => { - if (node.type.name === "table") { + if (node.type.name === CORE_EXTENSIONS.TABLE) { return false; } - if (["tableCell", "tableHeader"].includes(node.type.name)) { + if ([CORE_EXTENSIONS.TABLE_CELL, CORE_EXTENSIONS.TABLE_HEADER].includes(node.type.name as CORE_EXTENSIONS)) { cellCount += 1; } }); diff --git a/packages/editor/src/core/extensions/table/table/utilities/insert-line-above-table-action.ts b/packages/editor/src/core/extensions/table/table/utilities/insert-line-above-table-action.ts index ca5ed3d7e87..35c2ee3c713 100644 --- a/packages/editor/src/core/extensions/table/table/utilities/insert-line-above-table-action.ts +++ b/packages/editor/src/core/extensions/table/table/utilities/insert-line-above-table-action.ts @@ -1,17 +1,19 @@ import { KeyboardShortcutCommand } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // helpers import { findParentNodeOfType } from "@/helpers/common"; export const insertLineAboveTableAction: KeyboardShortcutCommand = ({ editor }) => { // Check if the current selection or the closest node is a table - if (!editor.isActive("table")) return false; + if (!editor.isActive(CORE_EXTENSIONS.TABLE)) return false; try { // Get the current selection const { selection } = editor.state; // Find the table node and its position - const tableNode = findParentNodeOfType(selection, "table"); + const tableNode = findParentNodeOfType(selection, CORE_EXTENSIONS.TABLE); if (!tableNode) return false; const tablePos = tableNode.pos; @@ -39,7 +41,7 @@ export const insertLineAboveTableAction: KeyboardShortcutCommand = ({ editor }) const prevNode = editor.state.doc.nodeAt(prevNodePos - 1); - if (prevNode && prevNode.type.name === "paragraph") { + if (prevNode && prevNode.type.name === CORE_EXTENSIONS.PARAGRAPH) { // If there's a paragraph before the table, move the cursor to the end of that paragraph const endOfParagraphPos = tablePos - prevNode.nodeSize; editor.chain().setTextSelection(endOfParagraphPos).run(); diff --git a/packages/editor/src/core/extensions/table/table/utilities/insert-line-below-table-action.ts b/packages/editor/src/core/extensions/table/table/utilities/insert-line-below-table-action.ts index 7edca9f30df..6c26e22a2f6 100644 --- a/packages/editor/src/core/extensions/table/table/utilities/insert-line-below-table-action.ts +++ b/packages/editor/src/core/extensions/table/table/utilities/insert-line-below-table-action.ts @@ -1,17 +1,19 @@ import { KeyboardShortcutCommand } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // helpers import { findParentNodeOfType } from "@/helpers/common"; export const insertLineBelowTableAction: KeyboardShortcutCommand = ({ editor }) => { // Check if the current selection or the closest node is a table - if (!editor.isActive("table")) return false; + if (!editor.isActive(CORE_EXTENSIONS.TABLE)) return false; try { // Get the current selection const { selection } = editor.state; // Find the table node and its position - const tableNode = findParentNodeOfType(selection, "table"); + const tableNode = findParentNodeOfType(selection, CORE_EXTENSIONS.TABLE); if (!tableNode) return false; const tablePos = tableNode.pos; @@ -31,13 +33,13 @@ export const insertLineBelowTableAction: KeyboardShortcutCommand = ({ editor }) // Check for an existing node immediately after the table const nextNode = editor.state.doc.nodeAt(nextNodePos); - if (nextNode && nextNode.type.name === "paragraph") { + if (nextNode && nextNode.type.name === CORE_EXTENSIONS.PARAGRAPH) { // If the next node is an paragraph, move the cursor there const endOfParagraphPos = nextNodePos + nextNode.nodeSize - 1; editor.chain().setTextSelection(endOfParagraphPos).run(); } else if (!nextNode) { // If the next node doesn't exist i.e. we're at the end of the document, create and insert a new empty node there - editor.chain().insertContentAt(nextNodePos, { type: "paragraph" }).run(); + editor.chain().insertContentAt(nextNodePos, { type: CORE_EXTENSIONS.PARAGRAPH }).run(); editor .chain() .setTextSelection(nextNodePos + 1) diff --git a/packages/editor/src/core/extensions/work-item-embed/widget-node.tsx b/packages/editor/src/core/extensions/work-item-embed/extension.tsx similarity index 100% rename from packages/editor/src/core/extensions/work-item-embed/widget-node.tsx rename to packages/editor/src/core/extensions/work-item-embed/extension.tsx diff --git a/packages/editor/src/core/extensions/work-item-embed/index.ts b/packages/editor/src/core/extensions/work-item-embed/index.ts index 459bafe024c..2ce32da8ba5 100644 --- a/packages/editor/src/core/extensions/work-item-embed/index.ts +++ b/packages/editor/src/core/extensions/work-item-embed/index.ts @@ -1,2 +1 @@ -export * from "./widget-node"; -export * from "./extension-config"; +export * from "./extension"; diff --git a/packages/editor/src/core/helpers/common.ts b/packages/editor/src/core/helpers/common.ts index 974b111d09f..ab3da88a28e 100644 --- a/packages/editor/src/core/helpers/common.ts +++ b/packages/editor/src/core/helpers/common.ts @@ -1,6 +1,8 @@ import { EditorState, Selection } from "@tiptap/pm/state"; -// plane utils +// plane imports import { cn } from "@plane/utils"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; interface EditorClassNames { noBorder?: boolean; @@ -91,7 +93,7 @@ export const getParagraphCount = (editorState: EditorState | undefined) => { if (!editorState) return 0; let paragraphCount = 0; editorState.doc.descendants((node) => { - if (node.type.name === "paragraph" && node.content.size > 0) paragraphCount++; + if (node.type.name === CORE_EXTENSIONS.PARAGRAPH && node.content.size > 0) paragraphCount++; }); return paragraphCount; }; diff --git a/packages/editor/src/core/helpers/editor-commands.ts b/packages/editor/src/core/helpers/editor-commands.ts index e8c98ada573..ec7977023c4 100644 --- a/packages/editor/src/core/helpers/editor-commands.ts +++ b/packages/editor/src/core/helpers/editor-commands.ts @@ -1,4 +1,6 @@ import { Editor, Range } from "@tiptap/core"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { InsertImageComponentProps } from "@/extensions"; import { replaceCodeWithText } from "@/extensions/code/utils/replace-code-block-with-text"; @@ -6,42 +8,42 @@ import { replaceCodeWithText } from "@/extensions/code/utils/replace-code-block- import { findTableAncestor } from "@/helpers/common"; export const setText = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("paragraph").run(); - else editor.chain().focus().setNode("paragraph").run(); + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.PARAGRAPH).run(); + else editor.chain().focus().setNode(CORE_EXTENSIONS.PARAGRAPH).run(); }; export const toggleHeadingOne = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 1 }).run(); // @ts-expect-error tiptap types are incorrect else editor.chain().focus().toggleHeading({ level: 1 }).run(); }; export const toggleHeadingTwo = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 2 }).run(); // @ts-expect-error tiptap types are incorrect else editor.chain().focus().toggleHeading({ level: 2 }).run(); }; export const toggleHeadingThree = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 3 }).run(); // @ts-expect-error tiptap types are incorrect else editor.chain().focus().toggleHeading({ level: 3 }).run(); }; export const toggleHeadingFour = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 4 }).run(); + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 4 }).run(); // @ts-expect-error tiptap types are incorrect else editor.chain().focus().toggleHeading({ level: 4 }).run(); }; export const toggleHeadingFive = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 5 }).run(); + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 5 }).run(); // @ts-expect-error tiptap types are incorrect else editor.chain().focus().toggleHeading({ level: 5 }).run(); }; export const toggleHeadingSix = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 6 }).run(); + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 6 }).run(); // @ts-expect-error tiptap types are incorrect else editor.chain().focus().toggleHeading({ level: 6 }).run(); }; @@ -68,7 +70,7 @@ export const toggleUnderline = (editor: Editor, range?: Range) => { export const toggleCodeBlock = (editor: Editor, range?: Range) => { try { // if it's a code block, replace it with the code with paragraphs - if (editor.isActive("codeBlock")) { + if (editor.isActive(CORE_EXTENSIONS.CODE_BLOCK)) { replaceCodeWithText(editor); return; } diff --git a/packages/editor/src/core/helpers/insert-empty-paragraph-at-node-boundary.ts b/packages/editor/src/core/helpers/insert-empty-paragraph-at-node-boundary.ts index ffad88d4e7e..b9449b494dd 100644 --- a/packages/editor/src/core/helpers/insert-empty-paragraph-at-node-boundary.ts +++ b/packages/editor/src/core/helpers/insert-empty-paragraph-at-node-boundary.ts @@ -1,5 +1,7 @@ import { KeyboardShortcutCommand } from "@tiptap/core"; import { Node as ProseMirrorNode } from "@tiptap/pm/model"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; type Direction = "up" | "down"; @@ -39,13 +41,13 @@ export const insertEmptyParagraphAtNodeBoundaries: ( if (insertPosUp === 0) { // If at the very start of the document, insert a new paragraph at the start - editor.chain().insertContentAt(insertPosUp, { type: "paragraph" }).run(); + editor.chain().insertContentAt(insertPosUp, { type: CORE_EXTENSIONS.PARAGRAPH }).run(); editor.chain().setTextSelection(insertPosUp).run(); // Set the cursor to the new paragraph } else { // Otherwise, check the node immediately before the target node const prevNode = doc.nodeAt(insertPosUp - 1); - if (prevNode && prevNode.type.name === "paragraph") { + if (prevNode && prevNode.type.name === CORE_EXTENSIONS.PARAGRAPH) { // If the previous node is a paragraph, move the cursor there editor .chain() @@ -67,13 +69,13 @@ export const insertEmptyParagraphAtNodeBoundaries: ( // Check the node immediately after the target node const nextNode = doc.nodeAt(insertPosDown); - if (nextNode && nextNode.type.name === "paragraph") { + if (nextNode && nextNode.type.name === CORE_EXTENSIONS.PARAGRAPH) { // If the next node is a paragraph, move the cursor to the end of it const endOfParagraphPos = insertPosDown + nextNode.nodeSize - 1; editor.chain().setTextSelection(endOfParagraphPos).run(); } else if (!nextNode) { // If there is no next node (end of document), insert a new paragraph - editor.chain().insertContentAt(insertPosDown, { type: "paragraph" }).run(); + editor.chain().insertContentAt(insertPosDown, { type: CORE_EXTENSIONS.PARAGRAPH }).run(); editor .chain() .setTextSelection(insertPosDown + 1) diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index c34527fa57e..b957a2e5db5 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -242,7 +242,7 @@ export const useEditor = (props: CustomEditorProps) => { const safePosition = Math.max(0, Math.min(position, docSize)); editor .chain() - .insertContentAt(safePosition, [{ type: "paragraph" }]) + .insertContentAt(safePosition, [{ type: CORE_EXTENSIONS.PARAGRAPH }]) .focus() .run(); } catch (error) { diff --git a/packages/editor/src/core/plugins/drag-handle.ts b/packages/editor/src/core/plugins/drag-handle.ts index f9a60a48c12..aa00fa32d90 100644 --- a/packages/editor/src/core/plugins/drag-handle.ts +++ b/packages/editor/src/core/plugins/drag-handle.ts @@ -2,6 +2,8 @@ import { Fragment, Slice, Node, Schema } from "@tiptap/pm/model"; import { NodeSelection } from "@tiptap/pm/state"; // @ts-expect-error __serializeForClipboard's is not exported import { __serializeForClipboard, EditorView } from "@tiptap/pm/view"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; // extensions import { SideMenuHandleOptions, SideMenuPluginProps } from "@/extensions"; @@ -132,7 +134,7 @@ export const DragHandlePlugin = (options: SideMenuPluginProps): SideMenuHandleOp let listType = ""; let isDragging = false; let lastClientY = 0; - let scrollAnimationFrame = null; + let scrollAnimationFrame: number | null = null; let isDraggedOutsideWindow: "top" | "bottom" | boolean = false; let isMouseInsideWhileDragging = false; let currentScrollSpeed = 0; @@ -142,8 +144,10 @@ export const DragHandlePlugin = (options: SideMenuPluginProps): SideMenuHandleOp }; const handleDragStart = (event: DragEvent, view: EditorView) => { - const { listType: listTypeFromDragStart } = handleNodeSelection(event, view, true, options); - listType = listTypeFromDragStart; + const { listType: listTypeFromDragStart } = handleNodeSelection(event, view, true, options) ?? {}; + if (listTypeFromDragStart) { + listType = listTypeFromDragStart; + } isDragging = true; lastClientY = event.clientY; scroll(); @@ -297,7 +301,7 @@ export const DragHandlePlugin = (options: SideMenuPluginProps): SideMenuHandleOp // Traverse up the document tree to find if we're inside a list item for (let i = resolvedPos.depth; i > 0; i--) { - if (resolvedPos.node(i).type.name === "listItem") { + if (resolvedPos.node(i).type.name === CORE_EXTENSIONS.LIST_ITEM) { isDroppedInsideList = true; dropDepth = i; break; @@ -305,7 +309,7 @@ export const DragHandlePlugin = (options: SideMenuPluginProps): SideMenuHandleOp } // Handle nested list items and task items - if (droppedNode.type.name === "listItem") { + if (droppedNode.type.name === CORE_EXTENSIONS.LIST_ITEM) { let slice = view.state.selection.content(); let newFragment = slice.content; @@ -348,8 +352,8 @@ function flattenListStructure(fragment: Fragment, schema: Schema): Fragment { (node.content.firstChild.type === schema.nodes.bulletList || node.content.firstChild.type === schema.nodes.orderedList) ) { - const sublist = node.content.firstChild; - const flattened = flattenListStructure(sublist.content, schema); + const subList = node.content.firstChild; + const flattened = flattenListStructure(subList.content, schema); flattened.forEach((subNode) => result.push(subNode)); } } @@ -376,7 +380,7 @@ const handleNodeSelection = ( let draggedNodePos = nodePosAtDOM(node, view, options); if (draggedNodePos == null || draggedNodePos < 0) return; - // Handle blockquotes separately + // Handle blockquote separately if (node.matches("blockquote")) { draggedNodePos = nodePosAtDOMForBlockQuotes(node, view); if (draggedNodePos === null || draggedNodePos === undefined) return; @@ -385,7 +389,10 @@ const handleNodeSelection = ( const $pos = view.state.doc.resolve(draggedNodePos); // If it's a nested list item or task item, move up to the item level - if (($pos.parent.type.name === "listItem" || $pos.parent.type.name === "taskItem") && $pos.depth > 1) { + if ( + [CORE_EXTENSIONS.LIST_ITEM, CORE_EXTENSIONS.TASK_ITEM].includes($pos.parent.type.name as CORE_EXTENSIONS) && + $pos.depth > 1 + ) { draggedNodePos = $pos.before($pos.depth); } } @@ -403,14 +410,16 @@ const handleNodeSelection = ( // Additional logic for drag start if (event instanceof DragEvent && !event.dataTransfer) return; - if (nodeSelection.node.type.name === "listItem" || nodeSelection.node.type.name === "taskItem") { + if ( + [CORE_EXTENSIONS.LIST_ITEM, CORE_EXTENSIONS.TASK_ITEM].includes(nodeSelection.node.type.name as CORE_EXTENSIONS) + ) { listType = node.closest("ol, ul")?.tagName || ""; } const slice = view.state.selection.content(); const { dom, text } = __serializeForClipboard(view, slice); - if (event instanceof DragEvent) { + if (event instanceof DragEvent && event.dataTransfer) { event.dataTransfer.clearData(); event.dataTransfer.setData("text/html", dom.innerHTML); event.dataTransfer.setData("text/plain", text); diff --git a/packages/editor/src/core/plugins/markdown-clipboard.ts b/packages/editor/src/core/plugins/markdown-clipboard.ts index 72676abd184..78f649b23d4 100644 --- a/packages/editor/src/core/plugins/markdown-clipboard.ts +++ b/packages/editor/src/core/plugins/markdown-clipboard.ts @@ -1,6 +1,8 @@ import { Editor } from "@tiptap/core"; import { Fragment, Node } from "@tiptap/pm/model"; import { Plugin, PluginKey } from "@tiptap/pm/state"; +// constants +import { CORE_EXTENSIONS } from "@/constants/extension"; export const MarkdownClipboardPlugin = (editor: Editor): Plugin => new Plugin({ @@ -8,7 +10,7 @@ export const MarkdownClipboardPlugin = (editor: Editor): Plugin => props: { clipboardTextSerializer: (slice) => { const markdownSerializer = editor.storage.markdown.serializer; - const isTableRow = slice.content.firstChild?.type?.name === "tableRow"; + const isTableRow = slice.content.firstChild?.type?.name === CORE_EXTENSIONS.TABLE_ROW; const nodeSelect = slice.openStart === 0 && slice.openEnd === 0; if (nodeSelect) { @@ -43,7 +45,7 @@ export const MarkdownClipboardPlugin = (editor: Editor): Plugin => while (currentNode && currentDepth > 1 && currentNode.content?.firstChild) { if (currentNode.content?.childCount > 1) { - if (currentNode.content.firstChild?.type?.name === "listItem") { + if (currentNode.content.firstChild?.type?.name === CORE_EXTENSIONS.LIST_ITEM) { return currentParent; } else { return currentNode.content; From 2ec62b2f83f9ad71b0c373fc0709d852023188b6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 22 May 2025 15:39:31 +0530 Subject: [PATCH 12/20] fix: errors and warnings --- .../src/ce/extensions/document-extensions.tsx | 1 - .../components/editors/link-view-container.tsx | 2 +- .../core/components/links/link-edit-view.tsx | 4 +++- .../menus/bubble-menu/node-selector.tsx | 2 +- .../src/core/extensions/callout/block.tsx | 2 +- .../core/extensions/callout/logo-selector.tsx | 7 ++----- .../editor/src/core/extensions/callout/types.ts | 2 +- .../editor/src/core/extensions/callout/utils.ts | 15 +++++++-------- .../extensions/code/code-block-node-view.tsx | 4 ++-- .../src/core/extensions/code/code-block.ts | 2 +- .../src/core/extensions/code/lowlight-plugin.ts | 2 +- .../{enter-key-extension.tsx => enter-key.tsx} | 0 packages/editor/src/core/extensions/headers.ts | 2 +- .../src/core/extensions/image/extension.tsx | 2 +- packages/editor/src/core/extensions/index.ts | 2 +- packages/editor/src/core/extensions/keymap.tsx | 4 ++-- .../mentions/mentions-list-dropdown.tsx | 10 +++++++--- .../src/core/extensions/mentions/utils.ts | 6 +++--- .../slash-commands/command-items-list.tsx | 4 ++-- .../extensions/slash-commands/command-menu.tsx | 6 ++++-- .../src/core/extensions/slash-commands/root.tsx | 4 ++-- .../table/{table-cell => }/table-cell.ts | 0 .../core/extensions/table/table-cell/index.ts | 1 - .../table/{table-header => }/table-header.ts | 0 .../core/extensions/table/table-header/index.ts | 1 - .../table/{table-row => }/table-row.ts | 0 .../core/extensions/table/table-row/index.ts | 1 - .../extensions/table/table/table-controls.ts | 4 ++-- .../core/extensions/table/table/table-view.tsx | 17 ++++++++--------- .../src/core/extensions/table/table/table.ts | 3 ++- packages/editor/src/core/helpers/common.ts | 4 ++-- .../editor/src/core/helpers/editor-commands.ts | 4 ++-- .../src/core/hooks/use-collaborative-editor.ts | 4 ++-- 33 files changed, 61 insertions(+), 61 deletions(-) rename packages/editor/src/core/extensions/{enter-key-extension.tsx => enter-key.tsx} (100%) rename packages/editor/src/core/extensions/table/{table-cell => }/table-cell.ts (100%) delete mode 100644 packages/editor/src/core/extensions/table/table-cell/index.ts rename packages/editor/src/core/extensions/table/{table-header => }/table-header.ts (100%) delete mode 100644 packages/editor/src/core/extensions/table/table-header/index.ts rename packages/editor/src/core/extensions/table/{table-row => }/table-row.ts (100%) delete mode 100644 packages/editor/src/core/extensions/table/table-row/index.ts diff --git a/packages/editor/src/ce/extensions/document-extensions.tsx b/packages/editor/src/ce/extensions/document-extensions.tsx index 445f5e0f809..29072b41c36 100644 --- a/packages/editor/src/ce/extensions/document-extensions.tsx +++ b/packages/editor/src/ce/extensions/document-extensions.tsx @@ -1,5 +1,4 @@ import { HocuspocusProvider } from "@hocuspocus/provider"; -import { Extensions } from "@tiptap/core"; import { AnyExtension } from "@tiptap/core"; import { SlashCommands } from "@/extensions"; // plane editor types diff --git a/packages/editor/src/core/components/editors/link-view-container.tsx b/packages/editor/src/core/components/editors/link-view-container.tsx index 41263a9962e..68fa33dde4f 100644 --- a/packages/editor/src/core/components/editors/link-view-container.tsx +++ b/packages/editor/src/core/components/editors/link-view-container.tsx @@ -12,7 +12,7 @@ interface LinkViewContainerProps { export const LinkViewContainer: FC = ({ editor, containerRef }) => { const [linkViewProps, setLinkViewProps] = useState(); const [isOpen, setIsOpen] = useState(false); - const [virtualElement, setVirtualElement] = useState(null); + const [virtualElement, setVirtualElement] = useState(null); const editorState = useEditorState({ editor, diff --git a/packages/editor/src/core/components/links/link-edit-view.tsx b/packages/editor/src/core/components/links/link-edit-view.tsx index ad66ce4b45a..1e9a62b0e1e 100644 --- a/packages/editor/src/core/components/links/link-edit-view.tsx +++ b/packages/editor/src/core/components/links/link-edit-view.tsx @@ -51,7 +51,9 @@ export const LinkEditView = ({ viewProps }: LinkEditViewProps) => { if (!hasSubmitted.current && !linkRemoved && initialUrl === "") { try { removeLink(); - } catch (e) {} + } catch (e) { + console.error("Error removing link", e); + } } }, [linkRemoved, initialUrl] diff --git a/packages/editor/src/core/components/menus/bubble-menu/node-selector.tsx b/packages/editor/src/core/components/menus/bubble-menu/node-selector.tsx index 7d1378800c9..564f7d97cab 100644 --- a/packages/editor/src/core/components/menus/bubble-menu/node-selector.tsx +++ b/packages/editor/src/core/components/menus/bubble-menu/node-selector.tsx @@ -1,6 +1,6 @@ -import { Dispatch, FC, SetStateAction } from "react"; import { Editor } from "@tiptap/react"; import { Check, ChevronDown } from "lucide-react"; +import { Dispatch, FC, SetStateAction } from "react"; // plane utils import { cn } from "@plane/utils"; // components diff --git a/packages/editor/src/core/extensions/callout/block.tsx b/packages/editor/src/core/extensions/callout/block.tsx index b6c6d7991bb..662a5ad3971 100644 --- a/packages/editor/src/core/extensions/callout/block.tsx +++ b/packages/editor/src/core/extensions/callout/block.tsx @@ -1,5 +1,5 @@ -import React, { useState } from "react"; import { NodeViewContent, NodeViewProps, NodeViewWrapper } from "@tiptap/react"; +import React, { useState } from "react"; // constants import { COLORS_LIST } from "@/constants/common"; // local components diff --git a/packages/editor/src/core/extensions/callout/logo-selector.tsx b/packages/editor/src/core/extensions/callout/logo-selector.tsx index 8ea47d50d0a..7a552cd16f0 100644 --- a/packages/editor/src/core/extensions/callout/logo-selector.tsx +++ b/packages/editor/src/core/extensions/callout/logo-selector.tsx @@ -1,9 +1,6 @@ -// plane helpers -import { convertHexEmojiToDecimal } from "@plane/utils"; -// plane ui +// plane imports import { EmojiIconPicker, EmojiIconPickerTypes, Logo, TEmojiLogoProps } from "@plane/ui"; -// plane utils -import { cn } from "@plane/utils"; +import { cn, convertHexEmojiToDecimal } from "@plane/utils"; // types import { TCalloutBlockAttributes } from "./types"; // utils diff --git a/packages/editor/src/core/extensions/callout/types.ts b/packages/editor/src/core/extensions/callout/types.ts index 17c55d9e5bb..8e650d87332 100644 --- a/packages/editor/src/core/extensions/callout/types.ts +++ b/packages/editor/src/core/extensions/callout/types.ts @@ -20,7 +20,7 @@ export type TCalloutBlockEmojiAttributes = { export type TCalloutBlockAttributes = { [EAttributeNames.LOGO_IN_USE]: "emoji" | "icon"; - [EAttributeNames.BACKGROUND]: string; + [EAttributeNames.BACKGROUND]: string | undefined; [EAttributeNames.BLOCK_TYPE]: "callout-component"; } & TCalloutBlockIconAttributes & TCalloutBlockEmojiAttributes; diff --git a/packages/editor/src/core/extensions/callout/utils.ts b/packages/editor/src/core/extensions/callout/utils.ts index 6568a40e3ea..3bf07f0a9ca 100644 --- a/packages/editor/src/core/extensions/callout/utils.ts +++ b/packages/editor/src/core/extensions/callout/utils.ts @@ -1,7 +1,6 @@ -// plane helpers -import { sanitizeHTML } from "@plane/utils"; -// plane ui +// plane imports import { TEmojiLogoProps } from "@plane/ui"; +import { sanitizeHTML } from "@plane/utils"; // types import { EAttributeNames, @@ -12,11 +11,11 @@ import { export const DEFAULT_CALLOUT_BLOCK_ATTRIBUTES: TCalloutBlockAttributes = { "data-logo-in-use": "emoji", - "data-icon-color": null, - "data-icon-name": null, + "data-icon-color": undefined, + "data-icon-name": undefined, "data-emoji-unicode": "128161", "data-emoji-url": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f4a1.png", - "data-background": null, + "data-background": undefined, "data-block-type": "callout-component", }; @@ -32,7 +31,7 @@ export const getStoredLogo = (): TStoredLogoValue => { }; if (typeof window !== "undefined") { - const storedData = sanitizeHTML(localStorage.getItem("editor-calloutComponent-logo")); + const storedData = sanitizeHTML(localStorage.getItem("editor-calloutComponent-logo") ?? ""); if (storedData) { let parsedData: TEmojiLogoProps; try { @@ -69,7 +68,7 @@ export const updateStoredLogo = (value: TEmojiLogoProps): void => { // function to get the stored background color from local storage export const getStoredBackgroundColor = (): string | null => { if (typeof window !== "undefined") { - return sanitizeHTML(localStorage.getItem("editor-calloutComponent-background")); + return sanitizeHTML(localStorage.getItem("editor-calloutComponent-background") ?? ""); } return null; }; diff --git a/packages/editor/src/core/extensions/code/code-block-node-view.tsx b/packages/editor/src/core/extensions/code/code-block-node-view.tsx index a06d839908a..7626031bc21 100644 --- a/packages/editor/src/core/extensions/code/code-block-node-view.tsx +++ b/packages/editor/src/core/extensions/code/code-block-node-view.tsx @@ -1,11 +1,11 @@ "use client"; -import { useState } from "react"; import { Node as ProseMirrorNode } from "@tiptap/pm/model"; import { NodeViewWrapper, NodeViewContent } from "@tiptap/react"; import ts from "highlight.js/lib/languages/typescript"; import { common, createLowlight } from "lowlight"; import { CopyIcon, CheckIcon } from "lucide-react"; +import { useState } from "react"; // ui import { Tooltip } from "@plane/ui"; // plane utils @@ -27,7 +27,7 @@ export const CodeBlockComponent: React.FC = ({ node }) await navigator.clipboard.writeText(node.textContent); setCopied(true); setTimeout(() => setCopied(false), 1000); - } catch (error) { + } catch { setCopied(false); } e.preventDefault(); diff --git a/packages/editor/src/core/extensions/code/code-block.ts b/packages/editor/src/core/extensions/code/code-block.ts index c246d45fbca..3b07617ca79 100644 --- a/packages/editor/src/core/extensions/code/code-block.ts +++ b/packages/editor/src/core/extensions/code/code-block.ts @@ -128,7 +128,7 @@ export const CodeBlock = Node.create({ return { "Mod-Alt-c": () => this.editor.commands.toggleCodeBlock(), - // remove code block when at start of document or code block is empty + // remove codeBlock when at start of document or codeBlock is empty Backspace: () => { try { const { empty, $anchor } = this.editor.state.selection; diff --git a/packages/editor/src/core/extensions/code/lowlight-plugin.ts b/packages/editor/src/core/extensions/code/lowlight-plugin.ts index 5ac30c27ea7..0b8ed71ad70 100644 --- a/packages/editor/src/core/extensions/code/lowlight-plugin.ts +++ b/packages/editor/src/core/extensions/code/lowlight-plugin.ts @@ -88,7 +88,7 @@ export function LowlightPlugin({ throw Error("You should provide an instance of lowlight to use the code-block-lowlight extension"); } - const lowlightPlugin: Plugin = new Plugin({ + const lowlightPlugin: Plugin = new Plugin({ key: new PluginKey("lowlight"), state: { diff --git a/packages/editor/src/core/extensions/enter-key-extension.tsx b/packages/editor/src/core/extensions/enter-key.tsx similarity index 100% rename from packages/editor/src/core/extensions/enter-key-extension.tsx rename to packages/editor/src/core/extensions/enter-key.tsx diff --git a/packages/editor/src/core/extensions/headers.ts b/packages/editor/src/core/extensions/headers.ts index a1a9303a08e..51a9aeedc26 100644 --- a/packages/editor/src/core/extensions/headers.ts +++ b/packages/editor/src/core/extensions/headers.ts @@ -14,7 +14,7 @@ export type HeadingExtensionStorage = { headings: IMarking[]; }; -export const HeadingListExtension = Extension.create({ +export const HeadingListExtension = Extension.create({ name: CORE_EXTENSIONS.HEADINGS_LIST, addStorage() { diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index 4dc63d722fe..4964c223112 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -17,7 +17,7 @@ export const ImageExtension = (fileHandler: TFileHandler) => { validation: { maxFileSize }, } = fileHandler; - return ImageExt.extend({ + return ImageExt.extend({ addKeyboardShortcuts() { return { ArrowDown: insertEmptyParagraphAtNodeBoundaries("down", this.name), diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts index 373623f4378..dd317543f4b 100644 --- a/packages/editor/src/core/extensions/index.ts +++ b/packages/editor/src/core/extensions/index.ts @@ -12,7 +12,7 @@ export * from "./typography"; export * from "./work-item-embed"; export * from "./core-without-props"; export * from "./custom-color"; -export * from "./enter-key-extension"; +export * from "./enter-key"; export * from "./extensions"; export * from "./headers"; export * from "./horizontal-rule"; diff --git a/packages/editor/src/core/extensions/keymap.tsx b/packages/editor/src/core/extensions/keymap.tsx index 783d7598d23..a4961bb9617 100644 --- a/packages/editor/src/core/extensions/keymap.tsx +++ b/packages/editor/src/core/extensions/keymap.tsx @@ -8,7 +8,7 @@ import { CORE_EXTENSIONS } from "@/constants/extension"; declare module "@tiptap/core" { // eslint-disable-next-line no-unused-vars interface Commands { - customkeymap: { + customKeymap: { /** * Select text between node boundaries */ @@ -61,7 +61,7 @@ function autoJoin(tr: Transaction, newTr: Transaction, nodeTypes: NodeType[]) { } export const CustomKeymap = Extension.create({ - name: "CustomKeymap", + name: "customKeymap", addCommands() { return { diff --git a/packages/editor/src/core/extensions/mentions/mentions-list-dropdown.tsx b/packages/editor/src/core/extensions/mentions/mentions-list-dropdown.tsx index 4f09ed2ae71..da11d0f9953 100644 --- a/packages/editor/src/core/extensions/mentions/mentions-list-dropdown.tsx +++ b/packages/editor/src/core/extensions/mentions/mentions-list-dropdown.tsx @@ -1,7 +1,7 @@ "use client"; -import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"; import { Editor } from "@tiptap/react"; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"; import { v4 as uuidv4 } from "uuid"; // plane utils import { cn } from "@plane/utils"; @@ -61,7 +61,9 @@ export const MentionsListDropdown = forwardRef((props: MentionsListDropdownProps sections, selectedIndex, }); - setSelectedIndex(newIndex); + if (newIndex) { + setSelectedIndex(newIndex); + } }, })); @@ -79,7 +81,9 @@ export const MentionsListDropdown = forwardRef((props: MentionsListDropdownProps setIsLoading(true); try { const sectionsResponse = await searchCallback?.(query); - setSections(sectionsResponse); + if (sectionsResponse) { + setSections(sectionsResponse); + } } catch (error) { console.error("Failed to fetch suggestions:", error); } finally { diff --git a/packages/editor/src/core/extensions/mentions/utils.ts b/packages/editor/src/core/extensions/mentions/utils.ts index e8e7ed4b7a9..5a7550c834d 100644 --- a/packages/editor/src/core/extensions/mentions/utils.ts +++ b/packages/editor/src/core/extensions/mentions/utils.ts @@ -1,7 +1,7 @@ import { Editor } from "@tiptap/core"; -import { SuggestionOptions } from "@tiptap/suggestion"; import { ReactRenderer } from "@tiptap/react"; -import tippy from "tippy.js"; +import { SuggestionOptions } from "@tiptap/suggestion"; +import tippy, { Instance } from "tippy.js"; // helpers import { CommandListInstance } from "@/helpers/tippy"; // types @@ -15,7 +15,7 @@ export const renderMentionsDropdown = () => { const { searchCallback } = props; let component: ReactRenderer | null = null; - let popup: any | null = null; + let popup: Instance | null = null; return { onStart: (props: { editor: Editor; clientRect: DOMRect }) => { 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 9fcc733aef6..9405a57807e 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 @@ -38,10 +38,10 @@ import { insertCallout, setText, } from "@/helpers/editor-commands"; -// types -import { CommandProps, ISlashCommandItem, TSlashCommandSectionKeys } from "@/types"; // plane editor extensions import { coreEditorAdditionalSlashCommandOptions } from "@/plane-editor/extensions"; +// types +import { CommandProps, ISlashCommandItem, TSlashCommandSectionKeys } from "@/types"; // local types import { TExtensionProps, TSlashCommandAdditionalOption } from "./root"; diff --git a/packages/editor/src/core/extensions/slash-commands/command-menu.tsx b/packages/editor/src/core/extensions/slash-commands/command-menu.tsx index 4ecd3f8fa2c..708e162cfc5 100644 --- a/packages/editor/src/core/extensions/slash-commands/command-menu.tsx +++ b/packages/editor/src/core/extensions/slash-commands/command-menu.tsx @@ -1,5 +1,5 @@ -import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"; import { Editor } from "@tiptap/core"; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"; // helpers import { DROPDOWN_NAVIGATION_KEYS, getNextValidIndex } from "@/helpers/tippy"; // components @@ -103,7 +103,9 @@ export const SlashCommandsMenu = forwardRef((props: SlashCommandsMenuProps, ref) sections, selectedIndex, }); - setSelectedIndex(newIndex); + if (newIndex) { + setSelectedIndex(newIndex); + } }, })); diff --git a/packages/editor/src/core/extensions/slash-commands/root.tsx b/packages/editor/src/core/extensions/slash-commands/root.tsx index a03912e50f0..828149d5027 100644 --- a/packages/editor/src/core/extensions/slash-commands/root.tsx +++ b/packages/editor/src/core/extensions/slash-commands/root.tsx @@ -1,7 +1,7 @@ import { Editor, Range, Extension } from "@tiptap/core"; import { ReactRenderer } from "@tiptap/react"; import Suggestion, { SuggestionOptions } from "@tiptap/suggestion"; -import tippy from "tippy.js"; +import tippy, { Instance } from "tippy.js"; // constants import { CORE_EXTENSIONS } from "@/constants/extension"; // helpers @@ -61,7 +61,7 @@ const Command = Extension.create({ const renderItems = () => { let component: ReactRenderer | null = null; - let popup: any | null = null; + let popup: Instance | null = null; return { onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => { component = new ReactRenderer(SlashCommandsMenu, { diff --git a/packages/editor/src/core/extensions/table/table-cell/table-cell.ts b/packages/editor/src/core/extensions/table/table-cell.ts similarity index 100% rename from packages/editor/src/core/extensions/table/table-cell/table-cell.ts rename to packages/editor/src/core/extensions/table/table-cell.ts diff --git a/packages/editor/src/core/extensions/table/table-cell/index.ts b/packages/editor/src/core/extensions/table/table-cell/index.ts deleted file mode 100644 index 68a25a9c3de..00000000000 --- a/packages/editor/src/core/extensions/table/table-cell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TableCell } from "./table-cell"; diff --git a/packages/editor/src/core/extensions/table/table-header/table-header.ts b/packages/editor/src/core/extensions/table/table-header.ts similarity index 100% rename from packages/editor/src/core/extensions/table/table-header/table-header.ts rename to packages/editor/src/core/extensions/table/table-header.ts diff --git a/packages/editor/src/core/extensions/table/table-header/index.ts b/packages/editor/src/core/extensions/table/table-header/index.ts deleted file mode 100644 index 290f37d0b78..00000000000 --- a/packages/editor/src/core/extensions/table/table-header/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TableHeader } from "./table-header"; diff --git a/packages/editor/src/core/extensions/table/table-row/table-row.ts b/packages/editor/src/core/extensions/table/table-row.ts similarity index 100% rename from packages/editor/src/core/extensions/table/table-row/table-row.ts rename to packages/editor/src/core/extensions/table/table-row.ts diff --git a/packages/editor/src/core/extensions/table/table-row/index.ts b/packages/editor/src/core/extensions/table/table-row/index.ts deleted file mode 100644 index 24dafb7e012..00000000000 --- a/packages/editor/src/core/extensions/table/table-row/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TableRow } from "./table-row"; diff --git a/packages/editor/src/core/extensions/table/table/table-controls.ts b/packages/editor/src/core/extensions/table/table/table-controls.ts index 086dc8b8e8a..d499b1b6a71 100644 --- a/packages/editor/src/core/extensions/table/table/table-controls.ts +++ b/packages/editor/src/core/extensions/table/table/table-controls.ts @@ -1,5 +1,5 @@ import { findParentNode } from "@tiptap/core"; -import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state"; +import { Plugin, PluginKey, TextSelection, Transaction } from "@tiptap/pm/state"; import { DecorationSet, Decoration } from "@tiptap/pm/view"; // constants import { CORE_EXTENSIONS } from "@/constants/extension"; @@ -112,7 +112,7 @@ class TableControlsState { }; } - apply(tr: any) { + apply(tr: Transaction) { const actions = tr.getMeta(key); if (actions?.setHoveredTable !== undefined) { diff --git a/packages/editor/src/core/extensions/table/table/table-view.tsx b/packages/editor/src/core/extensions/table/table/table-view.tsx index 801dd0b3fb9..f78d964ed49 100644 --- a/packages/editor/src/core/extensions/table/table/table-view.tsx +++ b/packages/editor/src/core/extensions/table/table/table-view.tsx @@ -1,12 +1,11 @@ -import { h } from "jsx-dom-cjs"; -import { Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model"; -import { Decoration, NodeView } from "@tiptap/pm/view"; -import tippy, { Instance, Props } from "tippy.js"; - import { Editor } from "@tiptap/core"; +import { Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model"; import { CellSelection, TableMap, updateColumnsOnResize } from "@tiptap/pm/tables"; - +import { Decoration, NodeView } from "@tiptap/pm/view"; +import { h } from "jsx-dom-cjs"; import { icons } from "src/core/extensions/table/table/icons"; +import tippy, { Instance, Props } from "tippy.js"; +// constants import { CORE_EXTENSIONS } from "@/constants/extension"; type ToolboxItem = { @@ -31,10 +30,10 @@ export function updateColumns( if (!row) return; for (let i = 0, col = 0; i < row.childCount; i += 1) { - const { colspan, colwidth } = row.child(i).attrs; + const { colspan, colWidth } = row.child(i).attrs; for (let j = 0; j < colspan; j += 1, col += 1) { - const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j]; + const hasWidth = overrideCol === col ? overrideValue : colWidth && colWidth[j]; const cssWidth = hasWidth ? `${hasWidth}px` : ""; totalWidth += hasWidth || cellMinWidth; @@ -86,7 +85,7 @@ function setCellsBackgroundColor(editor: Editor, color: { backgroundColor: strin return editor .chain() .focus() - .updateAttributes("tableCell", { + .updateAttributes(CORE_EXTENSIONS.TABLE_CELL, { background: color.backgroundColor, textColor: color.textColor, }) diff --git a/packages/editor/src/core/extensions/table/table/table.ts b/packages/editor/src/core/extensions/table/table/table.ts index 923f92123b9..4810706b395 100644 --- a/packages/editor/src/core/extensions/table/table/table.ts +++ b/packages/editor/src/core/extensions/table/table/table.ts @@ -19,6 +19,7 @@ import { toggleHeader, toggleHeaderCell, } from "@tiptap/pm/tables"; +import { Decoration } from "@tiptap/pm/view"; // constants import { CORE_EXTENSIONS } from "@/constants/extension"; // local imports @@ -251,7 +252,7 @@ export const Table = Node.create({ return ({ editor, getPos, node, decorations }) => { const { cellMinWidth } = this.options; - return new TableView(node, cellMinWidth, decorations as any, editor, getPos as () => number); + return new TableView(node, cellMinWidth, decorations as Decoration[], editor, getPos as () => number); }; }, diff --git a/packages/editor/src/core/helpers/common.ts b/packages/editor/src/core/helpers/common.ts index ab3da88a28e..e694e1e8539 100644 --- a/packages/editor/src/core/helpers/common.ts +++ b/packages/editor/src/core/helpers/common.ts @@ -69,7 +69,7 @@ export const isValidHttpUrl = (string: string): { isValid: boolean; url: string url: string, }; } - } catch (_) { + } catch { // Original string wasn't a valid URL - that's okay, we'll try with https } @@ -81,7 +81,7 @@ export const isValidHttpUrl = (string: string): { isValid: boolean; url: string isValid: true, url: urlWithHttps, }; - } catch (_) { + } catch { return { isValid: false, url: string, diff --git a/packages/editor/src/core/helpers/editor-commands.ts b/packages/editor/src/core/helpers/editor-commands.ts index ec7977023c4..38184bf4916 100644 --- a/packages/editor/src/core/helpers/editor-commands.ts +++ b/packages/editor/src/core/helpers/editor-commands.ts @@ -79,12 +79,12 @@ export const toggleCodeBlock = (editor: Editor, range?: Range) => { const text = editor.state.doc.textBetween(from, to, "\n"); const isMultiline = text.includes("\n"); - // if the selection is not a range i.e. empty, then simply convert it into a code block + // if the selection is not a range i.e. empty, then simply convert it into a codeBlock if (editor.state.selection.empty) { editor.chain().focus().toggleCodeBlock().run(); } else if (isMultiline) { // if the selection is multiline, then also replace the text content with - // a code block + // a codeBlock editor.chain().focus().deleteRange({ from, to }).insertContentAt(from, `\`\`\`\n${text}\n\`\`\``).run(); } else { // if the selection is single line, then simply convert it into inline diff --git a/packages/editor/src/core/hooks/use-collaborative-editor.ts b/packages/editor/src/core/hooks/use-collaborative-editor.ts index 4abf7d6d1ff..c5b976ccb79 100644 --- a/packages/editor/src/core/hooks/use-collaborative-editor.ts +++ b/packages/editor/src/core/hooks/use-collaborative-editor.ts @@ -1,6 +1,6 @@ -import { useEffect, useMemo, useState } from "react"; import { HocuspocusProvider } from "@hocuspocus/provider"; import Collaboration from "@tiptap/extension-collaboration"; +import { useEffect, useMemo, useState } from "react"; import { IndexeddbPersistence } from "y-indexeddb"; // extensions import { HeadingListExtension, SideMenuExtension } from "@/extensions"; @@ -76,7 +76,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { const editor = useEditor({ disabledExtensions, id, - editable, + editable: !!editable, editorProps, editorClassName, enableHistory: false, From 394621116bf6a064fe6f34ac5d7c7a5470b3e279 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 22 May 2025 16:50:41 +0530 Subject: [PATCH 13/20] refactor: rename extension files --- packages/editor/src/core/constants/extension.ts | 2 +- .../editor/src/core/extensions/{enter-key.tsx => enter-key.ts} | 0 .../src/core/extensions/{extensions.tsx => extensions.ts} | 0 .../editor/src/core/extensions/{headers.ts => headings-list.ts} | 0 packages/editor/src/core/extensions/index.ts | 2 +- packages/editor/src/core/extensions/{keymap.tsx => keymap.ts} | 0 packages/editor/src/core/extensions/{quote.tsx => quote.ts} | 0 .../{read-only-extensions.tsx => read-only-extensions.ts} | 0 .../editor/src/core/extensions/{side-menu.tsx => side-menu.ts} | 0 9 files changed, 2 insertions(+), 2 deletions(-) rename packages/editor/src/core/extensions/{enter-key.tsx => enter-key.ts} (100%) rename packages/editor/src/core/extensions/{extensions.tsx => extensions.ts} (100%) rename packages/editor/src/core/extensions/{headers.ts => headings-list.ts} (100%) rename packages/editor/src/core/extensions/{keymap.tsx => keymap.ts} (100%) rename packages/editor/src/core/extensions/{quote.tsx => quote.ts} (100%) rename packages/editor/src/core/extensions/{read-only-extensions.tsx => read-only-extensions.ts} (100%) rename packages/editor/src/core/extensions/{side-menu.tsx => side-menu.ts} (100%) diff --git a/packages/editor/src/core/constants/extension.ts b/packages/editor/src/core/constants/extension.ts index b8e6ca9afd5..db070cb7bfd 100644 --- a/packages/editor/src/core/constants/extension.ts +++ b/packages/editor/src/core/constants/extension.ts @@ -15,7 +15,7 @@ export enum CORE_EXTENSIONS { GAP_CURSOR = "gapCursor", HARD_BREAK = "hardBreak", HEADING = "heading", - HEADINGS_LIST = "headingList", + HEADINGS_LIST = "headingsList", HISTORY = "history", HORIZONTAL_RULE = "horizontalRule", IMAGE = "image", diff --git a/packages/editor/src/core/extensions/enter-key.tsx b/packages/editor/src/core/extensions/enter-key.ts similarity index 100% rename from packages/editor/src/core/extensions/enter-key.tsx rename to packages/editor/src/core/extensions/enter-key.ts diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.ts similarity index 100% rename from packages/editor/src/core/extensions/extensions.tsx rename to packages/editor/src/core/extensions/extensions.ts diff --git a/packages/editor/src/core/extensions/headers.ts b/packages/editor/src/core/extensions/headings-list.ts similarity index 100% rename from packages/editor/src/core/extensions/headers.ts rename to packages/editor/src/core/extensions/headings-list.ts diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts index dd317543f4b..3c3232885fc 100644 --- a/packages/editor/src/core/extensions/index.ts +++ b/packages/editor/src/core/extensions/index.ts @@ -14,7 +14,7 @@ export * from "./core-without-props"; export * from "./custom-color"; export * from "./enter-key"; export * from "./extensions"; -export * from "./headers"; +export * from "./headings-list"; export * from "./horizontal-rule"; export * from "./keymap"; export * from "./quote"; diff --git a/packages/editor/src/core/extensions/keymap.tsx b/packages/editor/src/core/extensions/keymap.ts similarity index 100% rename from packages/editor/src/core/extensions/keymap.tsx rename to packages/editor/src/core/extensions/keymap.ts diff --git a/packages/editor/src/core/extensions/quote.tsx b/packages/editor/src/core/extensions/quote.ts similarity index 100% rename from packages/editor/src/core/extensions/quote.tsx rename to packages/editor/src/core/extensions/quote.ts diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.ts similarity index 100% rename from packages/editor/src/core/extensions/read-only-extensions.tsx rename to packages/editor/src/core/extensions/read-only-extensions.ts diff --git a/packages/editor/src/core/extensions/side-menu.tsx b/packages/editor/src/core/extensions/side-menu.ts similarity index 100% rename from packages/editor/src/core/extensions/side-menu.tsx rename to packages/editor/src/core/extensions/side-menu.ts From 5491e320c34d9e51ef406564c0b60ddf1ac43945 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 27 May 2025 19:12:07 +0530 Subject: [PATCH 14/20] fix: tsup reloading issue --- live/package.json | 2 +- packages/decorators/package.json | 2 +- packages/hooks/package.json | 2 +- packages/ui/package.json | 2 +- packages/utils/package.json | 2 +- yarn.lock | 391 ++++++++++++++++++------------- 6 files changed, 228 insertions(+), 173 deletions(-) diff --git a/live/package.json b/live/package.json index 9616b151307..3dcb8b35ead 100644 --- a/live/package.json +++ b/live/package.json @@ -57,7 +57,7 @@ "concurrently": "^9.0.1", "nodemon": "^3.1.7", "ts-node": "^10.9.2", - "tsup": "^8.4.0", + "tsup": "8.3.0", "typescript": "5.3.3" } } diff --git a/packages/decorators/package.json b/packages/decorators/package.json index 433b5c11a4b..198fdc69808 100644 --- a/packages/decorators/package.json +++ b/packages/decorators/package.json @@ -27,7 +27,7 @@ "@types/node": "^20.14.9", "@types/reflect-metadata": "^0.1.0", "@types/ws": "^8.5.10", - "tsup": "8.4.0", + "tsup": "8.3.0", "typescript": "^5.3.3" }, "peerDependencies": { diff --git a/packages/hooks/package.json b/packages/hooks/package.json index d444bedda8b..320203e2d8d 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -22,7 +22,7 @@ "@plane/eslint-config": "*", "@types/node": "^22.5.4", "@types/react": "^18.3.11", - "tsup": "^8.4.0", + "tsup": "8.3.0", "typescript": "^5.3.3" } } diff --git a/packages/ui/package.json b/packages/ui/package.json index 45550f36979..e0920d49ec9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -71,7 +71,7 @@ "postcss-cli": "^11.0.0", "postcss-nested": "^6.0.1", "storybook": "^8.1.1", - "tsup": "^8.4.0", + "tsup": "8.3.0", "typescript": "5.3.3" } } diff --git a/packages/utils/package.json b/packages/utils/package.json index fc8600077d8..07504d9a48d 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -29,7 +29,7 @@ "@types/node": "^22.5.4", "@types/react": "^18.3.11", "@types/zxcvbn": "^4.4.5", - "tsup": "^8.4.0", + "tsup": "8.3.0", "typescript": "^5.3.3" } } diff --git a/yarn.lock b/yarn.lock index 6976451fa21..1d5db31352e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2236,100 +2236,105 @@ resolved "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== -"@rollup/rollup-android-arm-eabi@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz#e1d7700735f7e8de561ef7d1fa0362082a180c43" - integrity sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ== - -"@rollup/rollup-android-arm64@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz#fa6cdfb1fc9e2c8e227a7f35d524d8f7f90cf4db" - integrity sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA== - -"@rollup/rollup-darwin-arm64@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz#6da5a1ddc4f11d4a7ae85ab443824cb6bf614e30" - integrity sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q== - -"@rollup/rollup-darwin-x64@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz#25b74ce2d8d3f9ea8e119b01384d44a1c0a0d3ae" - integrity sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q== - -"@rollup/rollup-freebsd-arm64@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz#be3d39e3441df5d6e187c83d158c60656c82e203" - integrity sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ== - -"@rollup/rollup-freebsd-x64@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz#cd932d3ec679711efd65ca25821fb318e25b7ce4" - integrity sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw== - -"@rollup/rollup-linux-arm-gnueabihf@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz#d300b74c6f805474225632f185daaeae760ac2bb" - integrity sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg== - -"@rollup/rollup-linux-arm-musleabihf@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz#2caac622380f314c41934ed1e68ceaf6cc380cc3" - integrity sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A== - -"@rollup/rollup-linux-arm64-gnu@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz#1ec841650b038cc15c194c26326483fd7ebff3e3" - integrity sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A== - -"@rollup/rollup-linux-arm64-musl@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz#2fc70a446d986e27f6101ea74e81746987f69150" - integrity sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg== - -"@rollup/rollup-linux-loongarch64-gnu@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz#561bd045cd9ce9e08c95f42e7a8688af8c93d764" - integrity sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g== - -"@rollup/rollup-linux-powerpc64le-gnu@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz#45d849a0b33813f33fe5eba9f99e0ff15ab5caad" - integrity sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA== - -"@rollup/rollup-linux-riscv64-gnu@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz#78dde3e6fcf5b5733a97d0a67482d768aa1e83a5" - integrity sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g== - -"@rollup/rollup-linux-s390x-gnu@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz#2e34835020f9e03dfb411473a5c2a0e8a9c5037b" - integrity sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw== - -"@rollup/rollup-linux-x64-gnu@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz#4f9774beddc6f4274df57ac99862eb23040de461" - integrity sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA== - -"@rollup/rollup-linux-x64-musl@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz#dfcff2c1aed518b3d23ccffb49afb349d74fb608" - integrity sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg== - -"@rollup/rollup-win32-arm64-msvc@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz#b0b37e2d77041e3aa772f519291309abf4c03a84" - integrity sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg== - -"@rollup/rollup-win32-ia32-msvc@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz#5b5a40e44a743ddc0e06b8e1b3982f856dc9ce0a" - integrity sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw== - -"@rollup/rollup-win32-x64-msvc@4.35.0": - version "4.35.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz#05f25dbc9981bee1ae6e713daab10397044a46ca" - integrity sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw== +"@rollup/rollup-android-arm-eabi@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz#f39f09f60d4a562de727c960d7b202a2cf797424" + integrity sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw== + +"@rollup/rollup-android-arm64@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz#d19af7e23760717f1d879d4ca3d2cd247742dff2" + integrity sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA== + +"@rollup/rollup-darwin-arm64@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz#1c3a2fbf205d80641728e05f4a56c909e95218b7" + integrity sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w== + +"@rollup/rollup-darwin-x64@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz#aa66d2ba1a25e609500e13bef06dc0e71cc0c0d4" + integrity sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg== + +"@rollup/rollup-freebsd-arm64@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz#df10a7b6316a0ef1028c6ca71a081124c537e30d" + integrity sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg== + +"@rollup/rollup-freebsd-x64@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz#a3fdce8a05e95b068cbcb46e4df5185e407d0c35" + integrity sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA== + +"@rollup/rollup-linux-arm-gnueabihf@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz#49f766c55383bd0498014a9d76924348c2f3890c" + integrity sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg== + +"@rollup/rollup-linux-arm-musleabihf@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz#1d4d7d32fc557e17d52e1857817381ea365e2959" + integrity sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA== + +"@rollup/rollup-linux-arm64-gnu@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz#f4fc317268441e9589edad3be8f62b6c03009bc1" + integrity sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA== + +"@rollup/rollup-linux-arm64-musl@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz#63a1f1b0671cb17822dabae827fef0e443aebeb7" + integrity sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg== + +"@rollup/rollup-linux-loongarch64-gnu@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz#c659b01cc6c0730b547571fc3973e1e955369f98" + integrity sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw== + +"@rollup/rollup-linux-powerpc64le-gnu@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz#612e746f9ad7e58480f964d65e0d6c3f4aae69a8" + integrity sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A== + +"@rollup/rollup-linux-riscv64-gnu@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz#4610dbd1dcfbbae32fbc10c20ae7387acb31110c" + integrity sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw== + +"@rollup/rollup-linux-riscv64-musl@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz#054911fab40dc83fafc21e470193c058108f19d8" + integrity sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw== + +"@rollup/rollup-linux-s390x-gnu@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz#98896eca8012547c7f04bd07eaa6896825f9e1a5" + integrity sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g== + +"@rollup/rollup-linux-x64-gnu@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz#01cf56844a1e636ee80dfb364e72c2b7142ad896" + integrity sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A== + +"@rollup/rollup-linux-x64-musl@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz#e67c7531df6dff0b4c241101d4096617fbca87c3" + integrity sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ== + +"@rollup/rollup-win32-arm64-msvc@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz#7eeada98444e580674de6989284e4baacd48ea65" + integrity sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ== + +"@rollup/rollup-win32-ia32-msvc@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz#516c4b54f80587b4a390aaf4940b40870271d35d" + integrity sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg== + +"@rollup/rollup-win32-x64-msvc@4.41.1": + version "4.41.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz#848f99b0d9936d92221bb6070baeff4db6947a30" + integrity sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw== "@rtsao/scc@^1.1.0": version "1.1.0" @@ -3323,11 +3328,16 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.6": +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== +"@types/estree@1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" + integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": version "5.0.6" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz#41fec4ea20e9c7b22f024ab88a95c6bb288f51b8" @@ -4604,9 +4614,9 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -bundle-require@^5.1.0: +bundle-require@^5.0.0: version "5.1.0" - resolved "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee" + resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee" integrity sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== dependencies: load-tsconfig "^0.2.3" @@ -4761,13 +4771,6 @@ chokidar@^3.3.0, chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" -chokidar@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - chownr@^1.1.1: version "1.1.4" resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -4992,10 +4995,10 @@ concurrently@^9.0.1: tree-kill "^1.2.2" yargs "^17.7.2" -consola@^3.4.0: - version "3.4.0" - resolved "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz#4cfc9348fd85ed16a17940b3032765e31061ab88" - integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA== +consola@^3.2.3: + version "3.4.2" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== constant-case@^3.0.4: version "3.0.4" @@ -5099,7 +5102,7 @@ cross-fetch@^3.1.5: dependencies: node-fetch "^2.7.0" -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -5364,7 +5367,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@^4.4.0: +debug@4, debug@^4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: version "4.4.0" resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -5378,6 +5381,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.5: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + decimal.js-light@^2.4.1: version "2.5.1" resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" @@ -5930,7 +5940,7 @@ esbuild-register@^3.5.0: dependencies: debug "^4.3.4" -esbuild@0.25.0, "esbuild@^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0", esbuild@^0.25.0: +esbuild@0.25.0, "esbuild@^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0", esbuild@^0.23.0: version "0.25.0" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92" integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw== @@ -6261,6 +6271,21 @@ events@^3.2.0, events@^3.3.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + expand-template@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -6395,10 +6420,10 @@ fault@^2.0.0: dependencies: format "^0.2.0" -fdir@^6.4.3: - version "6.4.3" - resolved "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72" - integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw== +fdir@^6.4.4: + version "6.4.4" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.4.tgz#1cfcf86f875a883e19a8fab53622cfe992e8d2f9" + integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg== fecha@^4.2.0: version "4.2.3" @@ -6732,6 +6757,11 @@ get-stdin@^9.0.0: resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-symbol-description@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" @@ -7059,6 +7089,11 @@ https-proxy-agent@^7.0.6: agent-base "^7.1.2" debug "4" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + hyphen@^1.6.4: version "1.10.6" resolved "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz#0e779d280e696102b97d7e42f5ca5de2cc97e274" @@ -8281,6 +8316,11 @@ mime@1.6.0: resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -8542,6 +8582,13 @@ normalize.css@^8.0.1: resolved "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + nprogress@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" @@ -8680,6 +8727,13 @@ one-time@^1.0.0: dependencies: fn.name "1.x.x" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + open@^8.0.4: version "8.4.2" resolved "https://registry.npmjs.org/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" @@ -8862,7 +8916,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -9802,11 +9856,6 @@ readable-stream@^4.0.0: process "^0.11.10" string_decoder "^1.3.0" -readdirp@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" - integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -10067,32 +10116,33 @@ robust-predicates@^3.0.2: resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== -rollup@^4.34.8: - version "4.35.0" - resolved "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz#76c95dba17a579df4c00c3955aed32aa5d4dc66d" - integrity sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg== +rollup@^4.19.0: + version "4.41.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.41.1.tgz#46ddc1b33cf1b0baa99320d3b0b4973dc2253b6a" + integrity sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw== dependencies: - "@types/estree" "1.0.6" + "@types/estree" "1.0.7" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.35.0" - "@rollup/rollup-android-arm64" "4.35.0" - "@rollup/rollup-darwin-arm64" "4.35.0" - "@rollup/rollup-darwin-x64" "4.35.0" - "@rollup/rollup-freebsd-arm64" "4.35.0" - "@rollup/rollup-freebsd-x64" "4.35.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.35.0" - "@rollup/rollup-linux-arm-musleabihf" "4.35.0" - "@rollup/rollup-linux-arm64-gnu" "4.35.0" - "@rollup/rollup-linux-arm64-musl" "4.35.0" - "@rollup/rollup-linux-loongarch64-gnu" "4.35.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.35.0" - "@rollup/rollup-linux-riscv64-gnu" "4.35.0" - "@rollup/rollup-linux-s390x-gnu" "4.35.0" - "@rollup/rollup-linux-x64-gnu" "4.35.0" - "@rollup/rollup-linux-x64-musl" "4.35.0" - "@rollup/rollup-win32-arm64-msvc" "4.35.0" - "@rollup/rollup-win32-ia32-msvc" "4.35.0" - "@rollup/rollup-win32-x64-msvc" "4.35.0" + "@rollup/rollup-android-arm-eabi" "4.41.1" + "@rollup/rollup-android-arm64" "4.41.1" + "@rollup/rollup-darwin-arm64" "4.41.1" + "@rollup/rollup-darwin-x64" "4.41.1" + "@rollup/rollup-freebsd-arm64" "4.41.1" + "@rollup/rollup-freebsd-x64" "4.41.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.41.1" + "@rollup/rollup-linux-arm-musleabihf" "4.41.1" + "@rollup/rollup-linux-arm64-gnu" "4.41.1" + "@rollup/rollup-linux-arm64-musl" "4.41.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.41.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.41.1" + "@rollup/rollup-linux-riscv64-gnu" "4.41.1" + "@rollup/rollup-linux-riscv64-musl" "4.41.1" + "@rollup/rollup-linux-s390x-gnu" "4.41.1" + "@rollup/rollup-linux-x64-gnu" "4.41.1" + "@rollup/rollup-linux-x64-musl" "4.41.1" + "@rollup/rollup-win32-arm64-msvc" "4.41.1" + "@rollup/rollup-win32-ia32-msvc" "4.41.1" + "@rollup/rollup-win32-x64-msvc" "4.41.1" fsevents "~2.3.2" rope-sequence@^1.3.0: @@ -10399,6 +10449,11 @@ side-channel@^1.0.6, side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" @@ -10684,6 +10739,11 @@ strip-bom@^3.0.0: resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -10964,17 +11024,12 @@ tinycolor2@^1.4.1: resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== -tinyexec@^0.3.2: - version "0.3.2" - resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" - integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== - -tinyglobby@^0.2.11: - version "0.2.12" - resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5" - integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww== +tinyglobby@^0.2.1: + version "0.2.14" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" + integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== dependencies: - fdir "^6.4.3" + fdir "^6.4.4" picomatch "^4.0.2" tinyrainbow@^1.2.0: @@ -11147,26 +11202,26 @@ tslib@~2.5.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== -tsup@8.4.0, tsup@^8.4.0: - version "8.4.0" - resolved "https://registry.npmjs.org/tsup/-/tsup-8.4.0.tgz#2fdf537e7abc8f1ccbbbfe4228f16831457d4395" - integrity sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ== +tsup@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/tsup/-/tsup-8.3.0.tgz#c7dae40b13d11d81fb144c0f90077a99102a572a" + integrity sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag== dependencies: - bundle-require "^5.1.0" + bundle-require "^5.0.0" cac "^6.7.14" - chokidar "^4.0.3" - consola "^3.4.0" - debug "^4.4.0" - esbuild "^0.25.0" + chokidar "^3.6.0" + consola "^3.2.3" + debug "^4.3.5" + esbuild "^0.23.0" + execa "^5.1.1" joycon "^3.1.1" - picocolors "^1.1.1" + picocolors "^1.0.1" postcss-load-config "^6.0.1" resolve-from "^5.0.0" - rollup "^4.34.8" + rollup "^4.19.0" source-map "0.8.0-beta.0" sucrase "^3.35.0" - tinyexec "^0.3.2" - tinyglobby "^0.2.11" + tinyglobby "^0.2.1" tree-kill "^1.2.2" tsutils@^3.21.0: From c44a2096eb9dee8afb3204b846d728e80eac4cc8 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 27 May 2025 20:20:13 +0530 Subject: [PATCH 15/20] fix: image upload types and heading types --- packages/editor/package.json | 2 +- .../src/core/components/menus/menu-items.ts | 75 +++++++------------ .../components/image-uploader.tsx | 3 +- .../editor/src/core/extensions/extensions.ts | 1 + .../slash-commands/command-items-list.tsx | 19 ++--- .../slash-commands/command-menu.tsx | 3 +- .../src/core/helpers/editor-commands.ts | 36 +-------- 7 files changed, 44 insertions(+), 95 deletions(-) diff --git a/packages/editor/package.json b/packages/editor/package.json index cfbd0861e7d..5a899f738db 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -81,7 +81,7 @@ "@types/react": "^18.3.11", "@types/react-dom": "^18.2.18", "postcss": "^8.4.38", - "tsup": "^8.4.0", + "tsup": "8.3.0", "typescript": "5.3.3" }, "keywords": [ diff --git a/packages/editor/src/core/components/menus/menu-items.ts b/packages/editor/src/core/components/menus/menu-items.ts index 974251559e8..c3aa4d414a7 100644 --- a/packages/editor/src/core/components/menus/menu-items.ts +++ b/packages/editor/src/core/components/menus/menu-items.ts @@ -37,12 +37,7 @@ import { toggleBold, toggleBulletList, toggleCodeBlock, - toggleHeadingFive, - toggleHeadingFour, - toggleHeadingOne, - toggleHeadingSix, - toggleHeadingThree, - toggleHeadingTwo, + toggleHeading, toggleItalic, toggleOrderedList, toggleStrike, @@ -72,53 +67,39 @@ export const TextItem = (editor: Editor): EditorMenuItem<"text"> => ({ icon: CaseSensitive, }); -export const HeadingOneItem = (editor: Editor): EditorMenuItem<"h1"> => ({ - key: "h1", - name: "Heading 1", - isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 1 }), - command: () => toggleHeadingOne(editor), - icon: Heading1, -}); +type SupportedHeadingLevels = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; -export const HeadingTwoItem = (editor: Editor): EditorMenuItem<"h2"> => ({ - key: "h2", - name: "Heading 2", - isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 2 }), - command: () => toggleHeadingTwo(editor), - icon: Heading2, +const HeadingItem = ( + editor: Editor, + level: 1 | 2 | 3 | 4 | 5 | 6, + key: T, + name: string, + icon: LucideIcon +): EditorMenuItem => ({ + key, + name, + isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level }), + command: () => toggleHeading(editor, level), + icon, }); -export const HeadingThreeItem = (editor: Editor): EditorMenuItem<"h3"> => ({ - key: "h3", - name: "Heading 3", - isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 3 }), - command: () => toggleHeadingThree(editor), - icon: Heading3, -}); +export const HeadingOneItem = (editor: Editor): EditorMenuItem<"h1"> => + HeadingItem(editor, 1, "h1", "Heading 1", Heading1); -export const HeadingFourItem = (editor: Editor): EditorMenuItem<"h4"> => ({ - key: "h4", - name: "Heading 4", - isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 4 }), - command: () => toggleHeadingFour(editor), - icon: Heading4, -}); +export const HeadingTwoItem = (editor: Editor): EditorMenuItem<"h2"> => + HeadingItem(editor, 2, "h2", "Heading 2", Heading2); -export const HeadingFiveItem = (editor: Editor): EditorMenuItem<"h5"> => ({ - key: "h5", - name: "Heading 5", - isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 5 }), - command: () => toggleHeadingFive(editor), - icon: Heading5, -}); +export const HeadingThreeItem = (editor: Editor): EditorMenuItem<"h3"> => + HeadingItem(editor, 3, "h3", "Heading 3", Heading3); -export const HeadingSixItem = (editor: Editor): EditorMenuItem<"h6"> => ({ - key: "h6", - name: "Heading 6", - isActive: () => editor.isActive(CORE_EXTENSIONS.HEADING, { level: 6 }), - command: () => toggleHeadingSix(editor), - icon: Heading6, -}); +export const HeadingFourItem = (editor: Editor): EditorMenuItem<"h4"> => + HeadingItem(editor, 4, "h4", "Heading 4", Heading4); + +export const HeadingFiveItem = (editor: Editor): EditorMenuItem<"h5"> => + HeadingItem(editor, 5, "h5", "Heading 5", Heading5); + +export const HeadingSixItem = (editor: Editor): EditorMenuItem<"h6"> => + HeadingItem(editor, 6, "h6", "Heading 6", Heading6); export const BoldItem = (editor: Editor): EditorMenuItem<"bold"> => ({ key: "bold", 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 c7fe60c8567..5af4f556d72 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 @@ -9,6 +9,7 @@ import { CORE_EXTENSIONS } from "@/constants/extension"; import { CustoBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image"; // hooks import { useUploader, useDropZone, uploadFirstFileAndInsertRemaining } from "@/hooks/use-file-upload"; +import { getExtensionStorage } from "@/helpers/get-extension-storage"; type CustomImageUploaderProps = CustoBaseImageNodeViewProps & { maxFileSize: number; @@ -76,7 +77,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { // @ts-expect-error - TODO: fix typings, and don't remove await from here for now editorCommand: async (file) => await editor?.commands.uploadImage(imageEntityId, file), handleProgressStatus: (isUploading) => { - editor.storage.utility.uploadInProgress = isUploading; + getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY).uploadInProgress = isUploading; }, loadFileFromFileSystem: loadImageFromFileSystem, maxFileSize, diff --git a/packages/editor/src/core/extensions/extensions.ts b/packages/editor/src/core/extensions/extensions.ts index a50a1fd1b6f..a16994fc809 100644 --- a/packages/editor/src/core/extensions/extensions.ts +++ b/packages/editor/src/core/extensions/extensions.ts @@ -146,6 +146,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { if (node.type.name === CORE_EXTENSIONS.HEADING) return `Heading ${node.attrs.level}`; const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress; + if (isUploadInProgress) return ""; const shouldHidePlaceholder = 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 9405a57807e..fe9ec06a6d9 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 @@ -26,12 +26,7 @@ import { toggleBulletList, toggleOrderedList, toggleTaskList, - toggleHeadingOne, - toggleHeadingTwo, - toggleHeadingThree, - toggleHeadingFour, - toggleHeadingFive, - toggleHeadingSix, + toggleHeading, toggleTextColor, toggleBackgroundColor, insertImage, @@ -75,7 +70,7 @@ export const getSlashCommandFilteredSections = description: "Big section heading.", searchTerms: ["title", "big", "large"], icon: , - command: ({ editor, range }) => toggleHeadingOne(editor, range), + command: ({ editor, range }) => toggleHeading(editor, 1, range), }, { commandKey: "h2", @@ -84,7 +79,7 @@ export const getSlashCommandFilteredSections = description: "Medium section heading.", searchTerms: ["subtitle", "medium"], icon: , - command: ({ editor, range }) => toggleHeadingTwo(editor, range), + command: ({ editor, range }) => toggleHeading(editor, 2, range), }, { commandKey: "h3", @@ -93,7 +88,7 @@ export const getSlashCommandFilteredSections = description: "Small section heading.", searchTerms: ["subtitle", "small"], icon: , - command: ({ editor, range }) => toggleHeadingThree(editor, range), + command: ({ editor, range }) => toggleHeading(editor, 3, range), }, { commandKey: "h4", @@ -102,7 +97,7 @@ export const getSlashCommandFilteredSections = description: "Small section heading.", searchTerms: ["subtitle", "small"], icon: , - command: ({ editor, range }) => toggleHeadingFour(editor, range), + command: ({ editor, range }) => toggleHeading(editor, 4, range), }, { commandKey: "h5", @@ -111,7 +106,7 @@ export const getSlashCommandFilteredSections = description: "Small section heading.", searchTerms: ["subtitle", "small"], icon: , - command: ({ editor, range }) => toggleHeadingFive(editor, range), + command: ({ editor, range }) => toggleHeading(editor, 5, range), }, { commandKey: "h6", @@ -120,7 +115,7 @@ export const getSlashCommandFilteredSections = description: "Small section heading.", searchTerms: ["subtitle", "small"], icon: , - command: ({ editor, range }) => toggleHeadingSix(editor, range), + command: ({ editor, range }) => toggleHeading(editor, 6, range), }, { commandKey: "to-do-list", diff --git a/packages/editor/src/core/extensions/slash-commands/command-menu.tsx b/packages/editor/src/core/extensions/slash-commands/command-menu.tsx index 708e162cfc5..9d85266f2b2 100644 --- a/packages/editor/src/core/extensions/slash-commands/command-menu.tsx +++ b/packages/editor/src/core/extensions/slash-commands/command-menu.tsx @@ -3,13 +3,14 @@ import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffec // helpers import { DROPDOWN_NAVIGATION_KEYS, getNextValidIndex } from "@/helpers/tippy"; // components +import { ISlashCommandItem } from "@/types"; import { TSlashCommandSection } from "./command-items-list"; import { CommandMenuItem } from "./command-menu-item"; export type SlashCommandsMenuProps = { editor: Editor; items: TSlashCommandSection[]; - command: any; + command: (item: ISlashCommandItem) => void; }; export const SlashCommandsMenu = forwardRef((props: SlashCommandsMenuProps, ref) => { diff --git a/packages/editor/src/core/helpers/editor-commands.ts b/packages/editor/src/core/helpers/editor-commands.ts index 38184bf4916..5fa15cb08dd 100644 --- a/packages/editor/src/core/helpers/editor-commands.ts +++ b/packages/editor/src/core/helpers/editor-commands.ts @@ -12,40 +12,10 @@ export const setText = (editor: Editor, range?: Range) => { else editor.chain().focus().setNode(CORE_EXTENSIONS.PARAGRAPH).run(); }; -export const toggleHeadingOne = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 1 }).run(); +export const toggleHeading = (editor: Editor, level: 1 | 2 | 3 | 4 | 5 | 6, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level }).run(); // @ts-expect-error tiptap types are incorrect - else editor.chain().focus().toggleHeading({ level: 1 }).run(); -}; - -export const toggleHeadingTwo = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 2 }).run(); - // @ts-expect-error tiptap types are incorrect - else editor.chain().focus().toggleHeading({ level: 2 }).run(); -}; - -export const toggleHeadingThree = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 3 }).run(); - // @ts-expect-error tiptap types are incorrect - else editor.chain().focus().toggleHeading({ level: 3 }).run(); -}; - -export const toggleHeadingFour = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 4 }).run(); - // @ts-expect-error tiptap types are incorrect - else editor.chain().focus().toggleHeading({ level: 4 }).run(); -}; - -export const toggleHeadingFive = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 5 }).run(); - // @ts-expect-error tiptap types are incorrect - else editor.chain().focus().toggleHeading({ level: 5 }).run(); -}; - -export const toggleHeadingSix = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode(CORE_EXTENSIONS.HEADING, { level: 6 }).run(); - // @ts-expect-error tiptap types are incorrect - else editor.chain().focus().toggleHeading({ level: 6 }).run(); + else editor.chain().focus().toggleHeading({ level }).run(); }; export const toggleBold = (editor: Editor, range?: Range) => { From dbca7d3bf37dd6e88d450fe724af86935dedfd83 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 27 May 2025 21:04:29 +0530 Subject: [PATCH 16/20] fix: file plugin object reference --- packages/editor/src/core/plugins/file/delete.ts | 10 ++++++---- packages/editor/src/core/plugins/file/restore.ts | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/editor/src/core/plugins/file/delete.ts b/packages/editor/src/core/plugins/file/delete.ts index 3d7e16fe8e7..b77841c2298 100644 --- a/packages/editor/src/core/plugins/file/delete.ts +++ b/packages/editor/src/core/plugins/file/delete.ts @@ -7,9 +7,11 @@ import { TFileHandler } from "@/types"; // local imports import { TFileNode } from "./types"; +const DELETE_PLUGIN_KEY = new PluginKey("delete-utility"); + export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHandler["delete"]): Plugin => new Plugin({ - key: new PluginKey("delete-utility"), + key: DELETE_PLUGIN_KEY, appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { const newFileSources: { [nodeType: string]: Set | undefined; @@ -20,10 +22,10 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand const nodeType = node.type.name; const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; if (nodeFileSetDetails) { - if (newFileSources.nodeType) { - newFileSources.nodeType.add(node.attrs.src); + if (newFileSources[nodeType]) { + newFileSources[nodeType].add(node.attrs.src); } else { - newFileSources.nodeType = new Set([node.attrs.src]); + newFileSources[nodeType] = new Set([node.attrs.src]); } } }); diff --git a/packages/editor/src/core/plugins/file/restore.ts b/packages/editor/src/core/plugins/file/restore.ts index 791c3ce2fae..04a4c295ccd 100644 --- a/packages/editor/src/core/plugins/file/restore.ts +++ b/packages/editor/src/core/plugins/file/restore.ts @@ -9,9 +9,11 @@ import { TFileHandler } from "@/types"; // local imports import { TFileNode } from "./types"; +const RESTORE_PLUGIN_KEY = new PluginKey("restore-utility"); + export const TrackFileRestorationPlugin = (editor: Editor, restoreHandler: TFileHandler["restore"]): Plugin => new Plugin({ - key: new PluginKey("restore-utility"), + key: RESTORE_PLUGIN_KEY, appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { if (!transactions.some((tr) => tr.docChanged)) return null; @@ -22,10 +24,10 @@ export const TrackFileRestorationPlugin = (editor: Editor, restoreHandler: TFile const nodeType = node.type.name; const nodeFileSetDetails = NODE_FILE_MAP[nodeType]; if (nodeFileSetDetails) { - if (oldFileSources.nodeType) { - oldFileSources.nodeType.add(node.attrs.src); + if (oldFileSources[nodeType]) { + oldFileSources[nodeType].add(node.attrs.src); } else { - oldFileSources.nodeType = new Set([node.attrs.src]); + oldFileSources[nodeType] = new Set([node.attrs.src]); } } }); From 5b067d3a1b18dc7a671165d40b4cc2ce6a7f24ba Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 27 May 2025 23:02:15 +0530 Subject: [PATCH 17/20] fix: iseditable is hard coded --- packages/editor/src/core/extensions/extensions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/core/extensions/extensions.ts b/packages/editor/src/core/extensions/extensions.ts index a16994fc809..51969cd5cfa 100644 --- a/packages/editor/src/core/extensions/extensions.ts +++ b/packages/editor/src/core/extensions/extensions.ts @@ -50,7 +50,7 @@ type TArguments = { }; export const CoreEditorExtensions = (args: TArguments): Extensions => { - const { disabledExtensions, enableHistory, fileHandler, mentionHandler, placeholder, tabIndex } = args; + const { disabledExtensions, enableHistory, fileHandler, mentionHandler, placeholder, tabIndex, editable } = args; const extensions = [ StarterKit.configure({ @@ -170,7 +170,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { CustomTextAlignExtension, CustomCalloutExtension, UtilityExtension({ - isEditable: true, + isEditable: editable, fileHandler, }), CustomColorExtension, From 46b951ca6120ee7299ef81e7b1068e7f2c5ea631 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 27 May 2025 23:13:03 +0530 Subject: [PATCH 18/20] fix: image extension names --- .../src/core/extensions/core-without-props.ts | 4 +- .../extensions/custom-image/custom-image.ts | 4 +- .../custom-image/read-only-custom-image.ts | 4 +- .../src/core/extensions/image/extension.tsx | 4 +- .../image/image-component-without-props.tsx | 96 +++++++++---------- .../image/image-extension-without-props.tsx | 35 ++++--- .../core/extensions/image/read-only-image.tsx | 4 +- 7 files changed, 75 insertions(+), 76 deletions(-) diff --git a/packages/editor/src/core/extensions/core-without-props.ts b/packages/editor/src/core/extensions/core-without-props.ts index f71e8241e08..a309c2013af 100644 --- a/packages/editor/src/core/extensions/core-without-props.ts +++ b/packages/editor/src/core/extensions/core-without-props.ts @@ -72,12 +72,12 @@ export const CoreEditorExtensionsWithoutProps = [ "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", }, }), - ImageExtensionWithoutProps().configure({ + ImageExtensionWithoutProps.configure({ HTMLAttributes: { class: "rounded-md", }, }), - CustomImageComponentWithoutProps(), + CustomImageComponentWithoutProps, TiptapUnderline, TextStyle, TaskList.configure({ 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 c9cabf847ec..afd02fd099b 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -1,5 +1,5 @@ import { Editor, mergeAttributes } from "@tiptap/core"; -import { Image } from "@tiptap/extension-image"; +import { Image as BaseImageExtension } from "@tiptap/extension-image"; import { ReactNodeViewRenderer } from "@tiptap/react"; import { v4 as uuidv4 } from "uuid"; // constants @@ -50,7 +50,7 @@ export const CustomImageExtension = (props: TFileHandler) => { validation: { maxFileSize }, } = props; - return Image.extend, CustomImageExtensionStorage>({ + return BaseImageExtension.extend, CustomImageExtensionStorage>({ name: CORE_EXTENSIONS.CUSTOM_IMAGE, selectable: true, group: "block", 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 c1af38ec1af..4a85ffd94cb 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 @@ -1,5 +1,5 @@ import { mergeAttributes } from "@tiptap/core"; -import { Image } from "@tiptap/extension-image"; +import { Image as BaseImageExtension } from "@tiptap/extension-image"; import { ReactNodeViewRenderer } from "@tiptap/react"; // constants import { CORE_EXTENSIONS } from "@/constants/extension"; @@ -11,7 +11,7 @@ import { TReadOnlyFileHandler } from "@/types"; export const CustomReadOnlyImageExtension = (props: TReadOnlyFileHandler) => { const { getAssetSrc, restore: restoreImageFn } = props; - return Image.extend, CustomImageExtensionStorage>({ + return BaseImageExtension.extend, CustomImageExtensionStorage>({ name: CORE_EXTENSIONS.CUSTOM_IMAGE, selectable: false, group: "block", diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index 4964c223112..12844149cf8 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -1,4 +1,4 @@ -import ImageExt from "@tiptap/extension-image"; +import { Image as BaseImageExtension } from "@tiptap/extension-image"; import { ReactNodeViewRenderer } from "@tiptap/react"; // extensions import { CustomImageNode } from "@/extensions"; @@ -17,7 +17,7 @@ export const ImageExtension = (fileHandler: TFileHandler) => { validation: { maxFileSize }, } = fileHandler; - return ImageExt.extend({ + return BaseImageExtension.extend({ addKeyboardShortcuts() { return { ArrowDown: insertEmptyParagraphAtNodeBoundaries("down", this.name), 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 7d214556e91..bd2c3f16b5f 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 @@ -1,56 +1,56 @@ import { mergeAttributes } from "@tiptap/core"; -import { Image } from "@tiptap/extension-image"; +import { Image as BaseImageExtension } from "@tiptap/extension-image"; // local imports import { ImageExtensionStorage } from "./extension"; -export const CustomImageComponentWithoutProps = () => - Image.extend, ImageExtensionStorage>({ - name: "imageComponent", - selectable: true, - group: "block", - atom: true, - draggable: true, +export const CustomImageComponentWithoutProps = BaseImageExtension.extend< + Record, + ImageExtensionStorage +>({ + name: "imageComponent", + selectable: true, + group: "block", + atom: true, + draggable: true, - addAttributes() { - return { - ...this.parent?.(), - width: { - default: "35%", - }, - src: { - default: null, - }, - height: { - default: "auto", - }, - ["id"]: { - default: null, - }, - aspectRatio: { - default: null, - }, - }; - }, + addAttributes() { + return { + ...this.parent?.(), + width: { + default: "35%", + }, + src: { + default: null, + }, + height: { + default: "auto", + }, + ["id"]: { + default: null, + }, + aspectRatio: { + default: null, + }, + }; + }, - parseHTML() { - return [ - { - tag: "image-component", - }, - ]; - }, + parseHTML() { + return [ + { + tag: "image-component", + }, + ]; + }, - renderHTML({ HTMLAttributes }) { - return ["image-component", mergeAttributes(HTMLAttributes)]; - }, + renderHTML({ HTMLAttributes }) { + return ["image-component", mergeAttributes(HTMLAttributes)]; + }, - addStorage() { - return { - fileMap: new Map(), - deletedImageSet: new Map(), - maxFileSize: 0, - }; - }, - }); - -export default CustomImageComponentWithoutProps; + addStorage() { + return { + fileMap: new Map(), + deletedImageSet: new Map(), + maxFileSize: 0, + }; + }, +}); diff --git a/packages/editor/src/core/extensions/image/image-extension-without-props.tsx b/packages/editor/src/core/extensions/image/image-extension-without-props.tsx index bb6c5b4ad81..ba064bef485 100644 --- a/packages/editor/src/core/extensions/image/image-extension-without-props.tsx +++ b/packages/editor/src/core/extensions/image/image-extension-without-props.tsx @@ -1,19 +1,18 @@ -import ImageExt from "@tiptap/extension-image"; +import { Image as BaseImageExtension } from "@tiptap/extension-image"; -export const ImageExtensionWithoutProps = () => - ImageExt.extend({ - addAttributes() { - return { - ...this.parent?.(), - width: { - default: "35%", - }, - height: { - default: null, - }, - aspectRatio: { - default: null, - }, - }; - }, - }); +export const ImageExtensionWithoutProps = BaseImageExtension.extend({ + addAttributes() { + return { + ...this.parent?.(), + width: { + default: "35%", + }, + height: { + default: null, + }, + aspectRatio: { + default: null, + }, + }; + }, +}); 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 a656078037c..271c39fd8d5 100644 --- a/packages/editor/src/core/extensions/image/read-only-image.tsx +++ b/packages/editor/src/core/extensions/image/read-only-image.tsx @@ -1,4 +1,4 @@ -import Image from "@tiptap/extension-image"; +import { Image as BaseImageExtension } from "@tiptap/extension-image"; import { ReactNodeViewRenderer } from "@tiptap/react"; // extensions import { CustomImageNode } from "@/extensions"; @@ -8,7 +8,7 @@ import { TReadOnlyFileHandler } from "@/types"; export const ReadOnlyImageExtension = (props: TReadOnlyFileHandler) => { const { getAssetSrc } = props; - return Image.extend({ + return BaseImageExtension.extend({ addAttributes() { return { ...this.parent?.(), From 7fcb7981ae58ee0aa430b912e6ad6e0824b720c0 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 27 May 2025 23:37:09 +0530 Subject: [PATCH 19/20] fix: collaborative editor editable value --- .../core/components/editors/document/collaborative-editor.tsx | 1 + packages/editor/src/core/hooks/use-collaborative-editor.ts | 2 +- packages/editor/src/core/types/collaboration.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) 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 a7ff5503238..d1398ff5ae1 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -39,6 +39,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { } = props; const extensions: Extensions = []; + if (embedHandler?.issue) { extensions.push( WorkItemEmbedExtension({ diff --git a/packages/editor/src/core/hooks/use-collaborative-editor.ts b/packages/editor/src/core/hooks/use-collaborative-editor.ts index c5b976ccb79..8677b29edb9 100644 --- a/packages/editor/src/core/hooks/use-collaborative-editor.ts +++ b/packages/editor/src/core/hooks/use-collaborative-editor.ts @@ -76,7 +76,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { const editor = useEditor({ disabledExtensions, id, - editable: !!editable, + editable, editorProps, editorClassName, enableHistory: false, diff --git a/packages/editor/src/core/types/collaboration.ts b/packages/editor/src/core/types/collaboration.ts index 82e2f81f9a3..55608623204 100644 --- a/packages/editor/src/core/types/collaboration.ts +++ b/packages/editor/src/core/types/collaboration.ts @@ -22,7 +22,7 @@ export type TServerHandler = { type TCollaborativeEditorHookProps = { disabledExtensions: TExtensions[]; - editable?: boolean; + editable: boolean; editorClassName: string; editorProps?: EditorProps; extensions?: Extensions; From 8c80ace34466e206a2fe20917bc67be0ac1adf00 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 27 May 2025 23:46:26 +0530 Subject: [PATCH 20/20] chore: add constants for editor meta as well --- packages/editor/src/core/constants/meta.ts | 3 +++ packages/editor/src/core/hooks/use-editor.ts | 3 ++- packages/editor/src/core/hooks/use-read-only-editor.ts | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 packages/editor/src/core/constants/meta.ts diff --git a/packages/editor/src/core/constants/meta.ts b/packages/editor/src/core/constants/meta.ts new file mode 100644 index 00000000000..66769bb82c9 --- /dev/null +++ b/packages/editor/src/core/constants/meta.ts @@ -0,0 +1,3 @@ +export enum CORE_EDITOR_META { + SKIP_FILE_DELETION = "skipFileDeletion", +} diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index b957a2e5db5..a0cd739157c 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -26,6 +26,7 @@ import type { TExtensions, TMentionHandler, } from "@/types"; +import { CORE_EDITOR_META } from "@/constants/meta"; export interface CustomEditorProps { editable: boolean; @@ -149,7 +150,7 @@ export const useEditor = (props: CustomEditorProps) => { }, getCurrentCursorPosition: () => editor?.state.selection.from, clearEditor: (emitUpdate = false) => { - editor?.chain().setMeta("skipFileDeletion", true).clearContent(emitUpdate).run(); + editor?.chain().setMeta(CORE_EDITOR_META.SKIP_FILE_DELETION, true).clearContent(emitUpdate).run(); }, setEditorValue: (content: string, emitUpdate = false) => { editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" }); diff --git a/packages/editor/src/core/hooks/use-read-only-editor.ts b/packages/editor/src/core/hooks/use-read-only-editor.ts index 4762eff3e17..5bd731d5f19 100644 --- a/packages/editor/src/core/hooks/use-read-only-editor.ts +++ b/packages/editor/src/core/hooks/use-read-only-editor.ts @@ -12,6 +12,7 @@ import { IMarking, scrollSummary } from "@/helpers/scroll-to-node"; import { CoreReadOnlyEditorProps } from "@/props"; // types import type { EditorReadOnlyRefApi, TExtensions, TReadOnlyFileHandler, TReadOnlyMentionHandler } from "@/types"; +import { CORE_EDITOR_META } from "@/constants/meta"; interface CustomReadOnlyEditorProps { disabledExtensions: TExtensions[]; @@ -75,7 +76,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => { useImperativeHandle(forwardedRef, () => ({ clearEditor: (emitUpdate = false) => { - editor?.chain().setMeta("skipFileDeletion", true).clearContent(emitUpdate).run(); + editor?.chain().setMeta(CORE_EDITOR_META.SKIP_FILE_DELETION, true).clearContent(emitUpdate).run(); }, setEditorValue: (content: string, emitUpdate = false) => { editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" });