[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";
|
import React from "react";
|
||||||
// components
|
// components
|
||||||
import { DocumentContentLoader, PageRenderer } from "@/components/editors";
|
import { DocumentContentLoader, PageRenderer } from "@/components/editors";
|
||||||
|
|
@ -35,7 +36,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
|
||||||
user,
|
user,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const extensions = [];
|
const extensions: Extensions = [];
|
||||||
if (embedHandler?.issue) {
|
if (embedHandler?.issue) {
|
||||||
extensions.push(
|
extensions.push(
|
||||||
IssueWidget({
|
IssueWidget({
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Extensions } from "@tiptap/core";
|
||||||
import { forwardRef, MutableRefObject } from "react";
|
import { forwardRef, MutableRefObject } from "react";
|
||||||
// components
|
// components
|
||||||
import { PageRenderer } from "@/components/editors";
|
import { PageRenderer } from "@/components/editors";
|
||||||
|
|
@ -41,7 +42,7 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => {
|
||||||
initialValue,
|
initialValue,
|
||||||
mentionHandler,
|
mentionHandler,
|
||||||
} = props;
|
} = props;
|
||||||
const extensions = [];
|
const extensions: Extensions = [];
|
||||||
if (embedHandler?.issue) {
|
if (embedHandler?.issue) {
|
||||||
extensions.push(
|
extensions.push(
|
||||||
IssueWidget({
|
IssueWidget({
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export const AIFeaturesMenu: React.FC<Props> = (props) => {
|
||||||
menuRef.current.remove();
|
menuRef.current.remove();
|
||||||
menuRef.current.style.visibility = "visible";
|
menuRef.current.style.visibility = "visible";
|
||||||
|
|
||||||
|
// @ts-expect-error - Tippy types are incorrect
|
||||||
popup.current = tippy(document.body, {
|
popup.current = tippy(document.body, {
|
||||||
getReferenceClientRect: null,
|
getReferenceClientRect: null,
|
||||||
content: menuRef.current,
|
content: menuRef.current,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export const BlockMenu = (props: BlockMenuProps) => {
|
||||||
menuRef.current.remove();
|
menuRef.current.remove();
|
||||||
menuRef.current.style.visibility = "visible";
|
menuRef.current.style.visibility = "visible";
|
||||||
|
|
||||||
|
// @ts-expect-error - Tippy types are incorrect
|
||||||
popup.current = tippy(document.body, {
|
popup.current = tippy(document.body, {
|
||||||
getReferenceClientRect: null,
|
getReferenceClientRect: null,
|
||||||
content: menuRef.current,
|
content: menuRef.current,
|
||||||
|
|
|
||||||
|
|
@ -218,24 +218,33 @@ export const HorizontalRuleItem = (editor: Editor) =>
|
||||||
export const TextColorItem = (editor: Editor): EditorMenuItem<"text-color"> => ({
|
export const TextColorItem = (editor: Editor): EditorMenuItem<"text-color"> => ({
|
||||||
key: "text-color",
|
key: "text-color",
|
||||||
name: "Color",
|
name: "Color",
|
||||||
isActive: ({ color }) => editor.isActive("customColor", { color }),
|
isActive: (props) => editor.isActive("customColor", { color: props?.color }),
|
||||||
command: ({ color }) => toggleTextColor(color, editor),
|
command: (props) => {
|
||||||
|
if (!props) return;
|
||||||
|
toggleTextColor(props.color, editor);
|
||||||
|
},
|
||||||
icon: Palette,
|
icon: Palette,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BackgroundColorItem = (editor: Editor): EditorMenuItem<"background-color"> => ({
|
export const BackgroundColorItem = (editor: Editor): EditorMenuItem<"background-color"> => ({
|
||||||
key: "background-color",
|
key: "background-color",
|
||||||
name: "Background color",
|
name: "Background color",
|
||||||
isActive: ({ color }) => editor.isActive("customColor", { backgroundColor: color }),
|
isActive: (props) => editor.isActive("customColor", { backgroundColor: props?.color }),
|
||||||
command: ({ color }) => toggleBackgroundColor(color, editor),
|
command: (props) => {
|
||||||
|
if (!props) return;
|
||||||
|
toggleBackgroundColor(props.color, editor);
|
||||||
|
},
|
||||||
icon: Palette,
|
icon: Palette,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TextAlignItem = (editor: Editor): EditorMenuItem<"text-align"> => ({
|
export const TextAlignItem = (editor: Editor): EditorMenuItem<"text-align"> => ({
|
||||||
key: "text-align",
|
key: "text-align",
|
||||||
name: "Text align",
|
name: "Text align",
|
||||||
isActive: ({ alignment }) => editor.isActive({ textAlign: alignment }),
|
isActive: (props) => editor.isActive({ textAlign: props?.alignment }),
|
||||||
command: ({ alignment }) => setTextAlign(alignment, editor),
|
command: (props) => {
|
||||||
|
if (!props) return;
|
||||||
|
setTextAlign(props.alignment, editor);
|
||||||
|
},
|
||||||
icon: AlignCenter,
|
icon: AlignCenter,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,8 @@ export const CustomColorExtension = Mark.create({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// @ts-expect-error types are incorrect
|
||||||
|
// TODO: check this and update types
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from "react";
|
|
||||||
import { NodeSelection } from "@tiptap/pm/state";
|
import { NodeSelection } from "@tiptap/pm/state";
|
||||||
|
import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from "react";
|
||||||
// plane utils
|
// plane utils
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// extensions
|
// extensions
|
||||||
|
|
@ -38,11 +38,11 @@ const ensurePixelString = <TDefault,>(value: Pixel | TDefault | number | undefin
|
||||||
};
|
};
|
||||||
|
|
||||||
type CustomImageBlockProps = CustoBaseImageNodeViewProps & {
|
type CustomImageBlockProps = CustoBaseImageNodeViewProps & {
|
||||||
imageFromFileSystem: string;
|
imageFromFileSystem: string | undefined;
|
||||||
setFailedToLoadImage: (isError: boolean) => void;
|
setFailedToLoadImage: (isError: boolean) => void;
|
||||||
editorContainer: HTMLDivElement | null;
|
editorContainer: HTMLDivElement | null;
|
||||||
setEditorContainer: (editorContainer: HTMLDivElement | null) => void;
|
setEditorContainer: (editorContainer: HTMLDivElement | null) => void;
|
||||||
src: string;
|
src: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
|
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;
|
const { width: nodeWidth, height: nodeHeight, aspectRatio: nodeAspectRatio, src: imgNodeSrc } = node.attrs;
|
||||||
// states
|
// states
|
||||||
const [size, setSize] = useState<Size>({
|
const [size, setSize] = useState<Size>({
|
||||||
width: ensurePixelString(nodeWidth, "35%"),
|
width: ensurePixelString(nodeWidth, "35%") ?? "35%",
|
||||||
height: ensurePixelString(nodeHeight, "auto"),
|
height: ensurePixelString(nodeHeight, "auto") ?? "auto",
|
||||||
aspectRatio: nodeAspectRatio || null,
|
aspectRatio: nodeAspectRatio || null,
|
||||||
});
|
});
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
|
|
@ -144,8 +144,8 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setSize((prevSize) => ({
|
setSize((prevSize) => ({
|
||||||
...prevSize,
|
...prevSize,
|
||||||
width: ensurePixelString(nodeWidth),
|
width: ensurePixelString(nodeWidth) ?? "35%",
|
||||||
height: ensurePixelString(nodeHeight),
|
height: ensurePixelString(nodeHeight) ?? "auto",
|
||||||
aspectRatio: nodeAspectRatio,
|
aspectRatio: nodeAspectRatio,
|
||||||
}));
|
}));
|
||||||
}, [nodeWidth, nodeHeight, nodeAspectRatio]);
|
}, [nodeWidth, nodeHeight, nodeAspectRatio]);
|
||||||
|
|
@ -247,7 +247,16 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
|
||||||
try {
|
try {
|
||||||
setHasErroredOnFirstLoad(true);
|
setHasErroredOnFirstLoad(true);
|
||||||
// this is a type error from tiptap, don't remove await until it's fixed
|
// 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);
|
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;
|
imageRef.current.src = resolvedImageSrc;
|
||||||
} catch {
|
} catch {
|
||||||
// if the image failed to even restore, then show the error state
|
// if the image failed to even restore, then show the error state
|
||||||
|
|
@ -277,7 +286,7 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
|
||||||
}
|
}
|
||||||
image={{
|
image={{
|
||||||
src: resolvedImageSrc,
|
src: resolvedImageSrc,
|
||||||
aspectRatio: size.aspectRatio,
|
aspectRatio: size.aspectRatio === null ? 1 : size.aspectRatio,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { ChangeEvent, useCallback, useEffect, useMemo, useRef } from "react";
|
|
||||||
import { ImageIcon } from "lucide-react";
|
import { ImageIcon } from "lucide-react";
|
||||||
|
import { ChangeEvent, useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
// plane utils
|
// plane utils
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// constants
|
// constants
|
||||||
import { ACCEPTED_FILE_EXTENSIONS } from "@/constants/config";
|
import { ACCEPTED_FILE_EXTENSIONS } from "@/constants/config";
|
||||||
// hooks
|
|
||||||
import { useUploader, useDropZone, uploadFirstImageAndInsertRemaining } from "@/hooks/use-file-upload";
|
|
||||||
// extensions
|
// extensions
|
||||||
import { CustoBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image";
|
import { CustoBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image";
|
||||||
|
// hooks
|
||||||
|
import { useUploader, useDropZone, uploadFirstImageAndInsertRemaining } from "@/hooks/use-file-upload";
|
||||||
|
|
||||||
type CustomImageUploaderProps = CustoBaseImageNodeViewProps & {
|
type CustomImageUploaderProps = CustoBaseImageNodeViewProps & {
|
||||||
maxFileSize: number;
|
maxFileSize: number;
|
||||||
|
|
@ -38,6 +38,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
|
||||||
const onUpload = useCallback(
|
const onUpload = useCallback(
|
||||||
(url: string) => {
|
(url: string) => {
|
||||||
if (url) {
|
if (url) {
|
||||||
|
if (!imageEntityId) return;
|
||||||
setIsUploaded(true);
|
setIsUploaded(true);
|
||||||
// Update the node view's src attribute post upload
|
// Update the node view's src attribute post upload
|
||||||
updateAttributes({ src: url });
|
updateAttributes({ src: url });
|
||||||
|
|
@ -82,7 +83,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
|
||||||
|
|
||||||
// the meta data of the image component
|
// the meta data of the image component
|
||||||
const meta = useMemo(
|
const meta = useMemo(
|
||||||
() => imageComponentImageFileMap?.get(imageEntityId),
|
() => imageComponentImageFileMap?.get(imageEntityId ?? ""),
|
||||||
[imageComponentImageFileMap, imageEntityId]
|
[imageComponentImageFileMap, imageEntityId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -96,7 +97,7 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
|
||||||
if (meta.hasOpenedFileInputOnce) return;
|
if (meta.hasOpenedFileInputOnce) return;
|
||||||
fileInputRef.current.click();
|
fileInputRef.current.click();
|
||||||
hasTriggeredFilePickerRef.current = true;
|
hasTriggeredFilePickerRef.current = true;
|
||||||
imageComponentImageFileMap?.set(imageEntityId, { ...meta, hasOpenedFileInputOnce: true });
|
imageComponentImageFileMap?.set(imageEntityId ?? "", { ...meta, hasOpenedFileInputOnce: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [meta, uploadFile, imageComponentImageFileMap]);
|
}, [meta, uploadFile, imageComponentImageFileMap]);
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export const ImageFullScreenAction: React.FC<Props> = (props) => {
|
||||||
const dragStart = useRef({ x: 0, y: 0 });
|
const dragStart = useRef({ x: 0, y: 0 });
|
||||||
const dragOffset = useRef({ x: 0, y: 0 });
|
const dragOffset = useRef({ x: 0, y: 0 });
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
const widthInNumber = useMemo(() => Number(width?.replace("px", "")), [width]);
|
const widthInNumber = useMemo(() => Number(width?.replace("px", "")), [width]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ import {
|
||||||
} from "@/extensions";
|
} from "@/extensions";
|
||||||
// helpers
|
// helpers
|
||||||
import { isValidHttpUrl } from "@/helpers/common";
|
import { isValidHttpUrl } from "@/helpers/common";
|
||||||
// types
|
|
||||||
import { TExtensions, TFileHandler, TMentionHandler } from "@/types";
|
|
||||||
// plane editor extensions
|
// plane editor extensions
|
||||||
import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions";
|
import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions";
|
||||||
|
// types
|
||||||
|
import { TExtensions, TFileHandler, TMentionHandler } from "@/types";
|
||||||
|
|
||||||
type TArguments = {
|
type TArguments = {
|
||||||
disabledExtensions: TExtensions[];
|
disabledExtensions: TExtensions[];
|
||||||
|
|
@ -148,7 +148,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
|
||||||
CustomMentionExtension(mentionHandler),
|
CustomMentionExtension(mentionHandler),
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder: ({ editor, node }) => {
|
placeholder: ({ editor, node }) => {
|
||||||
if (!editor.isEditable) return;
|
if (!editor.isEditable) return "";
|
||||||
|
|
||||||
if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
|
if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ const renderItems = () => {
|
||||||
|
|
||||||
const tippyContainer =
|
const tippyContainer =
|
||||||
document.querySelector(".active-editor") ?? document.querySelector('[id^="editor-container"]');
|
document.querySelector(".active-editor") ?? document.querySelector('[id^="editor-container"]');
|
||||||
|
// @ts-expect-error - Tippy types are incorrect
|
||||||
popup = tippy("body", {
|
popup = tippy("body", {
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
appendTo: tippyContainer,
|
appendTo: tippyContainer,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Extension } from "@tiptap/core";
|
import { Extension, InputRule } from "@tiptap/core";
|
||||||
import {
|
import {
|
||||||
TypographyOptions,
|
TypographyOptions,
|
||||||
emDash,
|
emDash,
|
||||||
|
|
@ -26,7 +26,7 @@ export const CustomTypographyExtension = Extension.create<TypographyOptions>({
|
||||||
name: "typography",
|
name: "typography",
|
||||||
|
|
||||||
addInputRules() {
|
addInputRules() {
|
||||||
const rules = [];
|
const rules: InputRule[] = [];
|
||||||
|
|
||||||
if (this.options.emDash !== false) {
|
if (this.options.emDash !== false) {
|
||||||
rules.push(emDash(this.options.emDash));
|
rules.push(emDash(this.options.emDash));
|
||||||
|
|
|
||||||
|
|
@ -128,13 +128,13 @@ export const useEditor = (props: CustomEditorProps) => {
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
() => ({
|
() => ({
|
||||||
blur: () => editor.commands.blur(),
|
blur: () => editor?.commands.blur(),
|
||||||
scrollToNodeViaDOMCoordinates(behavior?: ScrollBehavior, pos?: number) {
|
scrollToNodeViaDOMCoordinates(behavior?: ScrollBehavior, pos?: number) {
|
||||||
const resolvedPos = pos ?? editor.state.selection.from;
|
const resolvedPos = pos ?? editor?.state.selection.from;
|
||||||
if (!editor || !resolvedPos) return;
|
if (!editor || !resolvedPos) return;
|
||||||
scrollToNodeViaDOMCoordinates(editor, resolvedPos, behavior);
|
scrollToNodeViaDOMCoordinates(editor, resolvedPos, behavior);
|
||||||
},
|
},
|
||||||
getCurrentCursorPosition: () => editor.state.selection.from,
|
getCurrentCursorPosition: () => editor?.state.selection.from,
|
||||||
clearEditor: (emitUpdate = false) => {
|
clearEditor: (emitUpdate = false) => {
|
||||||
editor?.chain().setMeta("skipImageDeletion", true).clearContent(emitUpdate).run();
|
editor?.chain().setMeta("skipImageDeletion", true).clearContent(emitUpdate).run();
|
||||||
},
|
},
|
||||||
|
|
@ -142,7 +142,7 @@ export const useEditor = (props: CustomEditorProps) => {
|
||||||
editor?.commands.setContent(content, false, { preserveWhitespace: "full" });
|
editor?.commands.setContent(content, false, { preserveWhitespace: "full" });
|
||||||
},
|
},
|
||||||
setEditorValueAtCursorPosition: (content: string) => {
|
setEditorValueAtCursorPosition: (content: string) => {
|
||||||
if (editor.state.selection) {
|
if (editor?.state.selection) {
|
||||||
insertContentAtSavedSelection(editor, content);
|
insertContentAtSavedSelection(editor, content);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -99,14 +99,11 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
scrollSummary(editor, marking);
|
scrollSummary(editor, marking);
|
||||||
},
|
},
|
||||||
getDocumentInfo: () => {
|
getDocumentInfo: () => ({
|
||||||
if (!editor) return;
|
characters: editor.storage?.characterCount?.characters?.() ?? 0,
|
||||||
return {
|
paragraphs: getParagraphCount(editor.state),
|
||||||
characters: editor.storage?.characterCount?.characters?.() ?? 0,
|
words: editor.storage?.characterCount?.words?.() ?? 0,
|
||||||
paragraphs: getParagraphCount(editor.state),
|
}),
|
||||||
words: editor.storage?.characterCount?.words?.() ?? 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"@/styles/*": ["src/styles/*"],
|
"@/styles/*": ["src/styles/*"],
|
||||||
"@/plane-editor/*": ["src/ce/*"]
|
"@/plane-editor/*": ["src/ce/*"]
|
||||||
},
|
},
|
||||||
|
"strictNullChecks": true,
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "index.d.ts"],
|
"include": ["src/**/*", "index.d.ts"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue