[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:
parent
f9d154dd82
commit
7e0ac10fe8
15 changed files with 63 additions and 39 deletions
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ export const CustomColorExtension = Mark.create({
|
|||
};
|
||||
},
|
||||
|
||||
// @ts-expect-error types are incorrect
|
||||
// TODO: check this and update types
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"@/styles/*": ["src/styles/*"],
|
||||
"@/plane-editor/*": ["src/ce/*"]
|
||||
},
|
||||
"strictNullChecks": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src/**/*", "index.d.ts"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue