[WIKI-181] refactor: invalid file handling #7139

This commit is contained in:
Aaryan Khandelwal 2025-05-30 18:18:05 +05:30 committed by GitHub
parent b16a585102
commit 01b685ea57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 23 additions and 50 deletions

View file

@ -86,6 +86,10 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
[editor] [editor]
); );
const handleInvalidFile = useCallback((_error: EFileError, _file: File, message: string) => {
alert(message);
}, []);
// hooks // hooks
const { isUploading: isImageBeingUploaded, uploadFile } = useUploader({ const { isUploading: isImageBeingUploaded, uploadFile } = useUploader({
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES, acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
@ -94,18 +98,12 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
handleProgressStatus, handleProgressStatus,
loadFileFromFileSystem: loadImageFromFileSystem, loadFileFromFileSystem: loadImageFromFileSystem,
maxFileSize, maxFileSize,
onInvalidFile: handleInvalidFile,
onUpload, onUpload,
}); });
const handleInvalidFile = useCallback((_error: EFileError, message: string) => {
alert(message);
}, []);
const { draggedInside, onDrop, onDragEnter, onDragLeave } = useDropZone({ const { draggedInside, onDrop, onDragEnter, onDragLeave } = useDropZone({
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
editor, editor,
maxFileSize,
onInvalidFile: handleInvalidFile,
pos: getPos(), pos: getPos(),
type: "image", type: "image",
uploader: uploadFile, uploader: uploadFile,
@ -140,11 +138,8 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
return; return;
} }
await uploadFirstFileAndInsertRemaining({ await uploadFirstFileAndInsertRemaining({
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
editor, editor,
filesList, filesList,
maxFileSize,
onInvalidFile: (_error, message) => alert(message),
pos: getPos(), pos: getPos(),
type: "image", type: "image",
uploader: uploadFile, uploader: uploadFile,

View file

@ -9,11 +9,11 @@ import { TEditorCommands } from "@/types";
type TUploaderArgs = { type TUploaderArgs = {
acceptedMimeTypes: string[]; acceptedMimeTypes: string[];
editorCommand: (file: File) => Promise<string>; editorCommand: (file: File) => Promise<string | undefined>;
handleProgressStatus?: (isUploading: boolean) => void; handleProgressStatus?: (isUploading: boolean) => void;
loadFileFromFileSystem?: (file: string) => void; loadFileFromFileSystem?: (file: string) => void;
maxFileSize: number; maxFileSize: number;
onInvalidFile: (error: EFileError, message: string) => void; onInvalidFile: (error: EFileError, file: File, message: string) => void;
onUpload: (url: string, file: File) => void; onUpload: (url: string, file: File) => void;
}; };
@ -38,7 +38,7 @@ export const useUploader = (args: TUploaderArgs) => {
acceptedMimeTypes, acceptedMimeTypes,
file, file,
maxFileSize, maxFileSize,
onError: onInvalidFile, onError: (error, message) => onInvalidFile(error, file, message),
}); });
if (!isValid) { if (!isValid) {
handleProgressStatus?.(false); handleProgressStatus?.(false);
@ -60,7 +60,7 @@ export const useUploader = (args: TUploaderArgs) => {
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
const url: string = await editorCommand(file); const url = await editorCommand(file);
if (!url) { if (!url) {
throw new Error("Something went wrong while uploading the file."); throw new Error("Something went wrong while uploading the file.");
@ -89,17 +89,14 @@ export const useUploader = (args: TUploaderArgs) => {
}; };
type TDropzoneArgs = { type TDropzoneArgs = {
acceptedMimeTypes: string[];
editor: Editor; editor: Editor;
maxFileSize: number;
onInvalidFile: (error: EFileError, message: string) => void;
pos: number; pos: number;
type: Extract<TEditorCommands, "attachment" | "image">; type: Extract<TEditorCommands, "attachment" | "image">;
uploader: (file: File) => Promise<void>; uploader: (file: File) => Promise<void>;
}; };
export const useDropZone = (args: TDropzoneArgs) => { export const useDropZone = (args: TDropzoneArgs) => {
const { acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader } = args; const { editor, pos, type, uploader } = args;
// states // states
const [isDragging, setIsDragging] = useState<boolean>(false); const [isDragging, setIsDragging] = useState<boolean>(false);
const [draggedInside, setDraggedInside] = useState<boolean>(false); const [draggedInside, setDraggedInside] = useState<boolean>(false);
@ -126,22 +123,21 @@ export const useDropZone = (args: TDropzoneArgs) => {
async (e: DragEvent<HTMLDivElement>) => { async (e: DragEvent<HTMLDivElement>) => {
e.preventDefault(); e.preventDefault();
setDraggedInside(false); setDraggedInside(false);
if (e.dataTransfer.files.length === 0 || !editor.isEditable) { const filesList = e.dataTransfer.files;
if (filesList.length === 0 || !editor.isEditable) {
return; return;
} }
const filesList = e.dataTransfer.files;
await uploadFirstFileAndInsertRemaining({ await uploadFirstFileAndInsertRemaining({
acceptedMimeTypes,
editor, editor,
filesList, filesList,
maxFileSize,
onInvalidFile,
pos, pos,
type, type,
uploader, uploader,
}); });
}, },
[acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader] [editor, pos, type, uploader]
); );
const onDragEnter = useCallback(() => setDraggedInside(true), []); const onDragEnter = useCallback(() => setDraggedInside(true), []);
const onDragLeave = useCallback(() => setDraggedInside(false), []); const onDragLeave = useCallback(() => setDraggedInside(false), []);
@ -156,11 +152,8 @@ export const useDropZone = (args: TDropzoneArgs) => {
}; };
type TMultipleFileArgs = { type TMultipleFileArgs = {
acceptedMimeTypes: string[];
editor: Editor; editor: Editor;
filesList: FileList; filesList: FileList;
maxFileSize: number;
onInvalidFile: (error: EFileError, message: string) => void;
pos: number; pos: number;
type: Extract<TEditorCommands, "attachment" | "image">; type: Extract<TEditorCommands, "attachment" | "image">;
uploader: (file: File) => Promise<void>; uploader: (file: File) => Promise<void>;
@ -168,35 +161,18 @@ type TMultipleFileArgs = {
// Upload the first file and insert the remaining ones for uploading multiple files // Upload the first file and insert the remaining ones for uploading multiple files
export const uploadFirstFileAndInsertRemaining = async (args: TMultipleFileArgs) => { export const uploadFirstFileAndInsertRemaining = async (args: TMultipleFileArgs) => {
const { acceptedMimeTypes, editor, filesList, maxFileSize, onInvalidFile, pos, type, uploader } = args; const { editor, filesList, pos, type, uploader } = args;
const filteredFiles: File[] = []; const filesArray = Array.from(filesList);
for (let i = 0; i < filesList.length; i += 1) { if (filesArray.length === 0) {
const file = filesList.item(i);
if (
file &&
isFileValid({
acceptedMimeTypes,
file,
maxFileSize,
onError: onInvalidFile,
})
) {
filteredFiles.push(file);
}
}
if (filteredFiles.length !== filesList.length) {
console.warn("Some files were invalid and have been ignored.");
}
if (filteredFiles.length === 0) {
console.error("No files found to upload."); console.error("No files found to upload.");
return; return;
} }
// Upload the first file // Upload the first file
const firstFile = filteredFiles[0]; const firstFile = filesArray[0];
uploader(firstFile); uploader(firstFile);
// Insert the remaining files // Insert the remaining files
const remainingFiles = filteredFiles.slice(1); const remainingFiles = filesArray.slice(1);
if (remainingFiles.length > 0) { if (remainingFiles.length > 0) {
const docSize = editor.state.doc.content.size; const docSize = editor.state.doc.content.size;
const posOfNextFileToBeInserted = Math.min(pos + 1, docSize); const posOfNextFileToBeInserted = Math.min(pos + 1, docSize);

View file

@ -1,5 +1,7 @@
import { Editor } from "@tiptap/core"; import { Editor } from "@tiptap/core";
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
// constants
import { CORE_EDITOR_META } from "@/constants/meta";
// plane editor imports // plane editor imports
import { NODE_FILE_MAP } from "@/plane-editor/constants/utility"; import { NODE_FILE_MAP } from "@/plane-editor/constants/utility";
// types // types
@ -32,7 +34,7 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand
transactions.forEach((transaction) => { transactions.forEach((transaction) => {
// if the transaction has meta of skipFileDeletion set to true, then return (like while clearing the editor content programmatically) // if the transaction has meta of skipFileDeletion set to true, then return (like while clearing the editor content programmatically)
if (transaction.getMeta("skipFileDeletion")) return; if (transaction.getMeta(CORE_EDITOR_META.SKIP_FILE_DELETION)) return;
const removedFiles: TFileNode[] = []; const removedFiles: TFileNode[] = [];