From 6a1d99faafcae5c9dd7a4e51df9f046707b7c591 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 21 Jan 2025 20:18:03 +0530 Subject: [PATCH 1/3] chore: add strictNullCheck flag --- packages/editor/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 8edd9106fc2..58b8640e694 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -12,6 +12,7 @@ "@/styles/*": ["src/styles/*"], "@/plane-editor/*": ["src/ce/*"] }, + "strictNullChecks": true, "allowSyntheticDefaultImports": true }, "include": ["src/**/*", "index.d.ts"], From 039499f63216af1619e26785ab28e3936ed627d3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 19 Feb 2025 14:53:16 +0530 Subject: [PATCH 2/3] fix: types and errors --- .../editors/document/collaborative-editor.tsx | 3 ++- .../editors/document/read-only-editor.tsx | 3 ++- .../src/core/components/menus/ai-menu.tsx | 1 + .../src/core/components/menus/block-menu.tsx | 1 + .../src/core/components/menus/menu-items.ts | 21 +++++++++++----- .../src/core/extensions/custom-color.ts | 1 + .../custom-image/components/image-block.tsx | 24 +++++++++++-------- .../components/image-uploader.tsx | 11 +++++---- .../components/toolbar/full-screen.tsx | 2 +- .../editor/src/core/extensions/extensions.tsx | 6 ++--- .../core/extensions/slash-commands/root.tsx | 1 + .../src/core/extensions/typography/index.ts | 4 ++-- packages/editor/src/core/hooks/use-editor.ts | 10 ++++---- .../src/core/hooks/use-read-only-editor.ts | 13 ++++------ 14 files changed, 59 insertions(+), 42 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 44c18c2f680..394cbf3bac0 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -1,3 +1,4 @@ +import { Extensions } from "@tiptap/core"; import React from "react"; // components import { DocumentContentLoader, PageRenderer } from "@/components/editors"; @@ -34,7 +35,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => { user, } = props; - const extensions = []; + const extensions: Extensions = []; if (embedHandler?.issue) { extensions.push( IssueWidget({ 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 0e8ab63f8a8..4516c56ef20 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 @@ -1,3 +1,4 @@ +import { Extensions } from "@tiptap/core"; import { forwardRef, MutableRefObject } from "react"; // components import { PageRenderer } from "@/components/editors"; @@ -41,7 +42,7 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => { initialValue, mentionHandler, } = props; - const extensions = []; + const extensions: Extensions = []; if (embedHandler?.issue) { extensions.push( IssueWidget({ diff --git a/packages/editor/src/core/components/menus/ai-menu.tsx b/packages/editor/src/core/components/menus/ai-menu.tsx index 42b45509f72..968c7d4690f 100644 --- a/packages/editor/src/core/components/menus/ai-menu.tsx +++ b/packages/editor/src/core/components/menus/ai-menu.tsx @@ -23,6 +23,7 @@ export const AIFeaturesMenu: React.FC = (props) => { menuRef.current.remove(); menuRef.current.style.visibility = "visible"; + // @ts-expect-error - Tippy types are incorrect popup.current = tippy(document.body, { getReferenceClientRect: null, content: menuRef.current, diff --git a/packages/editor/src/core/components/menus/block-menu.tsx b/packages/editor/src/core/components/menus/block-menu.tsx index 77d5ce81ec5..c143abd009c 100644 --- a/packages/editor/src/core/components/menus/block-menu.tsx +++ b/packages/editor/src/core/components/menus/block-menu.tsx @@ -34,6 +34,7 @@ export const BlockMenu = (props: BlockMenuProps) => { menuRef.current.remove(); menuRef.current.style.visibility = "visible"; + // @ts-expect-error - Tippy types are incorrect popup.current = tippy(document.body, { getReferenceClientRect: null, content: menuRef.current, diff --git a/packages/editor/src/core/components/menus/menu-items.ts b/packages/editor/src/core/components/menus/menu-items.ts index adbd262eb6e..4268ccb6c48 100644 --- a/packages/editor/src/core/components/menus/menu-items.ts +++ b/packages/editor/src/core/components/menus/menu-items.ts @@ -218,24 +218,33 @@ export const HorizontalRuleItem = (editor: Editor) => export const TextColorItem = (editor: Editor): EditorMenuItem<"text-color"> => ({ key: "text-color", name: "Color", - isActive: ({ color }) => editor.isActive("customColor", { color }), - command: ({ color }) => toggleTextColor(color, editor), + isActive: (props) => editor.isActive("customColor", { color: props?.color }), + command: (props) => { + if (!props) return; + toggleTextColor(props.color, editor); + }, icon: Palette, }); export const BackgroundColorItem = (editor: Editor): EditorMenuItem<"background-color"> => ({ key: "background-color", name: "Background color", - isActive: ({ color }) => editor.isActive("customColor", { backgroundColor: color }), - command: ({ color }) => toggleBackgroundColor(color, editor), + isActive: (props) => editor.isActive("customColor", { backgroundColor: props?.color }), + command: (props) => { + if (!props) return; + toggleBackgroundColor(props.color, editor); + }, icon: Palette, }); export const TextAlignItem = (editor: Editor): EditorMenuItem<"text-align"> => ({ key: "text-align", name: "Text align", - isActive: ({ alignment }) => editor.isActive({ textAlign: alignment }), - command: ({ alignment }) => setTextAlign(alignment, editor), + isActive: (props) => editor.isActive({ textAlign: props?.alignment }), + command: (props) => { + if (!props) return; + setTextAlign(props.alignment, editor); + }, icon: AlignCenter, }); diff --git a/packages/editor/src/core/extensions/custom-color.ts b/packages/editor/src/core/extensions/custom-color.ts index dc966816c56..0e8514bafbd 100644 --- a/packages/editor/src/core/extensions/custom-color.ts +++ b/packages/editor/src/core/extensions/custom-color.ts @@ -93,6 +93,7 @@ export const CustomColorExtension = Mark.create({ }; }, + // @ts-expect-error types are incorrect parseHTML() { return [ { diff --git a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx index 89194aae0c3..cdd5a4cb4e4 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx @@ -1,5 +1,5 @@ -import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from "react"; import { NodeSelection } from "@tiptap/pm/state"; +import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from "react"; // plane utils import { cn } from "@plane/utils"; // extensions @@ -38,11 +38,11 @@ const ensurePixelString = (value: Pixel | TDefault | number | undefin }; type CustomImageBlockProps = CustoBaseImageNodeViewProps & { - imageFromFileSystem: string; + imageFromFileSystem: string | undefined; setFailedToLoadImage: (isError: boolean) => void; editorContainer: HTMLDivElement | null; setEditorContainer: (editorContainer: HTMLDivElement | null) => void; - src: string; + src: string | undefined; }; export const CustomImageBlock: React.FC = (props) => { @@ -62,8 +62,8 @@ export const CustomImageBlock: React.FC = (props) => { const { width: nodeWidth, height: nodeHeight, aspectRatio: nodeAspectRatio, src: imgNodeSrc } = node.attrs; // states const [size, setSize] = useState({ - width: ensurePixelString(nodeWidth, "35%"), - height: ensurePixelString(nodeHeight, "auto"), + width: ensurePixelString(nodeWidth, "35%") ?? "35%", + height: ensurePixelString(nodeHeight, "auto") ?? "auto", aspectRatio: nodeAspectRatio || null, }); const [isResizing, setIsResizing] = useState(false); @@ -144,8 +144,8 @@ export const CustomImageBlock: React.FC = (props) => { useLayoutEffect(() => { setSize((prevSize) => ({ ...prevSize, - width: ensurePixelString(nodeWidth), - height: ensurePixelString(nodeHeight), + width: ensurePixelString(nodeWidth) ?? "35%", + height: ensurePixelString(nodeHeight) ?? "auto", aspectRatio: nodeAspectRatio, })); }, [nodeWidth, nodeHeight, nodeAspectRatio]); @@ -247,8 +247,12 @@ export const CustomImageBlock: React.FC = (props) => { try { setHasErroredOnFirstLoad(true); // this is a type error from tiptap, don't remove await until it's fixed - await editor?.commands.restoreImage?.(imgNodeSrc); - imageRef.current.src = resolvedImageSrc; + if (imgNodeSrc) { + await editor?.commands.restoreImage?.(imgNodeSrc); + } + if (imageRef.current && resolvedImageSrc) { + imageRef.current.src = resolvedImageSrc; + } } catch { // if the image failed to even restore, then show the error state setFailedToLoadImage(true); @@ -277,7 +281,7 @@ export const CustomImageBlock: React.FC = (props) => { } image={{ src: resolvedImageSrc, - aspectRatio: size.aspectRatio, + aspectRatio: size.aspectRatio === null ? 1 : size.aspectRatio, height: size.height, width: size.width, }} 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 eaea423878d..03505adb64d 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 @@ -1,13 +1,13 @@ -import { ChangeEvent, useCallback, useEffect, useMemo, useRef } from "react"; import { ImageIcon } from "lucide-react"; +import { ChangeEvent, useCallback, useEffect, useMemo, useRef } from "react"; // plane utils import { cn } from "@plane/utils"; // constants import { ACCEPTED_FILE_EXTENSIONS } from "@/constants/config"; -// hooks -import { useUploader, useDropZone, uploadFirstImageAndInsertRemaining } from "@/hooks/use-file-upload"; // extensions import { CustoBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image"; +// hooks +import { useUploader, useDropZone, uploadFirstImageAndInsertRemaining } from "@/hooks/use-file-upload"; type CustomImageUploaderProps = CustoBaseImageNodeViewProps & { maxFileSize: number; @@ -38,6 +38,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { const onUpload = useCallback( (url: string) => { if (url) { + if (!imageEntityId) return; setIsUploaded(true); // Update the node view's src attribute post upload updateAttributes({ src: url }); @@ -82,7 +83,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { // the meta data of the image component const meta = useMemo( - () => imageComponentImageFileMap?.get(imageEntityId), + () => imageComponentImageFileMap?.get(imageEntityId ?? ""), [imageComponentImageFileMap, imageEntityId] ); @@ -96,7 +97,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => { if (meta.hasOpenedFileInputOnce) return; fileInputRef.current.click(); hasTriggeredFilePickerRef.current = true; - imageComponentImageFileMap?.set(imageEntityId, { ...meta, hasOpenedFileInputOnce: true }); + imageComponentImageFileMap?.set(imageEntityId ?? "", { ...meta, hasOpenedFileInputOnce: true }); } } }, [meta, uploadFile, imageComponentImageFileMap]); diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx index 560d95cfc4c..61ae307bb66 100644 --- a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx @@ -29,7 +29,7 @@ export const ImageFullScreenAction: React.FC = (props) => { const dragStart = useRef({ x: 0, y: 0 }); const dragOffset = useRef({ x: 0, y: 0 }); const modalRef = useRef(null); - const imgRef = useRef(null); + const imgRef = useRef(null); const widthInNumber = useMemo(() => Number(width?.replace("px", "")), [width]); diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 0b772baf9db..f04ed756a64 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -32,10 +32,10 @@ import { } from "@/extensions"; // helpers import { isValidHttpUrl } from "@/helpers/common"; -// types -import { TExtensions, TFileHandler, TMentionHandler } from "@/types"; // plane editor extensions import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions"; +// types +import { TExtensions, TFileHandler, TMentionHandler } from "@/types"; type TArguments = { disabledExtensions: TExtensions[]; @@ -148,7 +148,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => { CustomMentionExtension(mentionHandler), Placeholder.configure({ placeholder: ({ editor, node }) => { - if (!editor.isEditable) return; + if (!editor.isEditable) return ""; if (node.type.name === "heading") return `Heading ${node.attrs.level}`; diff --git a/packages/editor/src/core/extensions/slash-commands/root.tsx b/packages/editor/src/core/extensions/slash-commands/root.tsx index 28aaebb8527..c0c078a2dda 100644 --- a/packages/editor/src/core/extensions/slash-commands/root.tsx +++ b/packages/editor/src/core/extensions/slash-commands/root.tsx @@ -69,6 +69,7 @@ const renderItems = () => { const tippyContainer = document.querySelector(".active-editor") ?? document.querySelector('[id^="editor-container"]'); + // @ts-expect-error - Tippy types are incorrect popup = tippy("body", { getReferenceClientRect: props.clientRect, appendTo: tippyContainer, diff --git a/packages/editor/src/core/extensions/typography/index.ts b/packages/editor/src/core/extensions/typography/index.ts index e9d48b41581..6b736953b53 100644 --- a/packages/editor/src/core/extensions/typography/index.ts +++ b/packages/editor/src/core/extensions/typography/index.ts @@ -1,4 +1,4 @@ -import { Extension } from "@tiptap/core"; +import { Extension, InputRule } from "@tiptap/core"; import { TypographyOptions, emDash, @@ -26,7 +26,7 @@ export const CustomTypographyExtension = Extension.create({ name: "typography", addInputRules() { - const rules = []; + const rules: InputRule[] = []; if (this.options.emDash !== false) { rules.push(emDash(this.options.emDash)); diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index af970b670a6..03e9353f4d7 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -128,13 +128,13 @@ export const useEditor = (props: CustomEditorProps) => { useImperativeHandle( forwardedRef, () => ({ - blur: () => editor.commands.blur(), + blur: () => editor?.commands.blur(), scrollToNodeViaDOMCoordinates(behavior?: ScrollBehavior, pos?: number) { - const resolvedPos = pos ?? editor.state.selection.from; + const resolvedPos = pos ?? editor?.state.selection.from; if (!editor || !resolvedPos) return; scrollToNodeViaDOMCoordinates(editor, resolvedPos, behavior); }, - getCurrentCursorPosition: () => editor.state.selection.from, + getCurrentCursorPosition: () => editor?.state.selection.from, clearEditor: (emitUpdate = false) => { editor?.chain().setMeta("skipImageDeletion", true).clearContent(emitUpdate).run(); }, @@ -142,7 +142,7 @@ export const useEditor = (props: CustomEditorProps) => { editor?.commands.setContent(content, false, { preserveWhitespace: "full" }); }, setEditorValueAtCursorPosition: (content: string) => { - if (editor.state.selection) { + if (editor?.state.selection) { insertContentAtSavedSelection(editor, content); } }, @@ -202,7 +202,7 @@ export const useEditor = (props: CustomEditorProps) => { getDocument: () => { const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null; const documentHTML = editor?.getHTML() ?? "

"; - const documentJSON = editor.getJSON() ?? null; + const documentJSON = editor?.getJSON() ?? null; return { binary: documentBinary, 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 12d4960b690..a0fa9f90c38 100644 --- a/packages/editor/src/core/hooks/use-read-only-editor.ts +++ b/packages/editor/src/core/hooks/use-read-only-editor.ts @@ -99,14 +99,11 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => { if (!editor) return; scrollSummary(editor, marking); }, - getDocumentInfo: () => { - if (!editor) return; - return { - characters: editor.storage?.characterCount?.characters?.() ?? 0, - paragraphs: getParagraphCount(editor.state), - words: editor.storage?.characterCount?.words?.() ?? 0, - }; - }, + getDocumentInfo: () => ({ + characters: editor.storage?.characterCount?.characters?.() ?? 0, + paragraphs: getParagraphCount(editor.state), + words: editor.storage?.characterCount?.words?.() ?? 0, + }), })); if (!editor) { From 32b798dc0f769a5e2a0fb8e20f27aa089fcde3f7 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 19 Feb 2025 15:11:29 +0530 Subject: [PATCH 3/3] chore: update error handling --- packages/editor/src/core/extensions/custom-color.ts | 1 + .../custom-image/components/image-block.tsx | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/core/extensions/custom-color.ts b/packages/editor/src/core/extensions/custom-color.ts index 745f0da1e31..b377099fb59 100644 --- a/packages/editor/src/core/extensions/custom-color.ts +++ b/packages/editor/src/core/extensions/custom-color.ts @@ -107,6 +107,7 @@ export const CustomColorExtension = Mark.create({ }, // @ts-expect-error types are incorrect + // TODO: check this and update types parseHTML() { return [ { diff --git a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx index cdd5a4cb4e4..961764081e5 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx @@ -247,12 +247,17 @@ export const CustomImageBlock: React.FC = (props) => { try { setHasErroredOnFirstLoad(true); // this is a type error from tiptap, don't remove await until it's fixed - if (imgNodeSrc) { - await editor?.commands.restoreImage?.(imgNodeSrc); + if (!imgNodeSrc) { + throw new Error("No source image to restore from"); } - if (imageRef.current && resolvedImageSrc) { - imageRef.current.src = resolvedImageSrc; + await editor?.commands.restoreImage?.(imgNodeSrc); + if (!imageRef.current) { + throw new Error("Image reference not found"); } + if (!resolvedImageSrc) { + throw new Error("No resolved image source available"); + } + imageRef.current.src = resolvedImageSrc; } catch { // if the image failed to even restore, then show the error state setFailedToLoadImage(true);