[PE-239] chore: add strictNullCheck flag to the editor package (#6439)

* chore: add strictNullCheck flag

* fix: types and errors

* chore: update error handling
This commit is contained in:
Aaryan Khandelwal 2025-02-19 15:13:37 +05:30 committed by GitHub
parent f9d154dd82
commit 7e0ac10fe8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 63 additions and 39 deletions

View file

@ -1,3 +1,4 @@
import { Extensions } from "@tiptap/core";
import React from "react";
// components
import { DocumentContentLoader, PageRenderer } from "@/components/editors";
@ -35,7 +36,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
user,
} = props;
const extensions = [];
const extensions: Extensions = [];
if (embedHandler?.issue) {
extensions.push(
IssueWidget({

View file

@ -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({

View file

@ -23,6 +23,7 @@ export const AIFeaturesMenu: React.FC<Props> = (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,

View file

@ -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,

View file

@ -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,
});

View file

@ -106,6 +106,8 @@ export const CustomColorExtension = Mark.create({
};
},
// @ts-expect-error types are incorrect
// TODO: check this and update types
parseHTML() {
return [
{

View file

@ -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 = <TDefault,>(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<CustomImageBlockProps> = (props) => {
@ -62,8 +62,8 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
const { width: nodeWidth, height: nodeHeight, aspectRatio: nodeAspectRatio, src: imgNodeSrc } = node.attrs;
// states
const [size, setSize] = useState<Size>({
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<CustomImageBlockProps> = (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,7 +247,16 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
try {
setHasErroredOnFirstLoad(true);
// this is a type error from tiptap, don't remove await until it's fixed
if (!imgNodeSrc) {
throw new Error("No source image to restore from");
}
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
@ -277,7 +286,7 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
}
image={{
src: resolvedImageSrc,
aspectRatio: size.aspectRatio,
aspectRatio: size.aspectRatio === null ? 1 : size.aspectRatio,
height: size.height,
width: size.width,
}}

View file

@ -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]);

View file

@ -29,7 +29,7 @@ export const ImageFullScreenAction: React.FC<Props> = (props) => {
const dragStart = useRef({ x: 0, y: 0 });
const dragOffset = useRef({ x: 0, y: 0 });
const modalRef = useRef<HTMLDivElement>(null);
const imgRef = useRef<HTMLImageElement>(null);
const imgRef = useRef<HTMLImageElement | null>(null);
const widthInNumber = useMemo(() => Number(width?.replace("px", "")), [width]);

View file

@ -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}`;

View file

@ -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,

View file

@ -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<TypographyOptions>({
name: "typography",
addInputRules() {
const rules = [];
const rules: InputRule[] = [];
if (this.options.emDash !== false) {
rules.push(emDash(this.options.emDash));

View file

@ -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);
}
},

View file

@ -99,14 +99,11 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
if (!editor) return;
scrollSummary(editor, marking);
},
getDocumentInfo: () => {
if (!editor) return;
return {
getDocumentInfo: () => ({
characters: editor.storage?.characterCount?.characters?.() ?? 0,
paragraphs: getParagraphCount(editor.state),
words: editor.storage?.characterCount?.words?.() ?? 0,
};
},
}),
}));
if (!editor) {

View file

@ -12,6 +12,7 @@
"@/styles/*": ["src/styles/*"],
"@/plane-editor/*": ["src/ce/*"]
},
"strictNullChecks": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*", "index.d.ts"],