diff --git a/src/cloud/components/Editor/index.tsx b/src/cloud/components/Editor/index.tsx index 639530cb0a..b986116cb0 100644 --- a/src/cloud/components/Editor/index.tsx +++ b/src/cloud/components/Editor/index.tsx @@ -390,6 +390,13 @@ const Editor = ({ ? fileUploadHandlerRef.current(file) : null }, + onUnsupportedFile: () => { + pushMessage({ + title: '', + description: + 'Folder uploads are not supported. Please drop individual files instead.', + }) + }, }) pasteFormatPlugin(editor, { openMenu: (pos, cb) => { @@ -487,7 +494,7 @@ const Editor = ({ ) }) }, - [handleCursorShowHintActivity] + [handleCursorShowHintActivity, pushMessage] ) const onTemplatePickCallback = useCallback( diff --git a/src/cloud/lib/editor/plugins/fileHandler.spec.ts b/src/cloud/lib/editor/plugins/fileHandler.spec.ts new file mode 100644 index 0000000000..50d352435b --- /dev/null +++ b/src/cloud/lib/editor/plugins/fileHandler.spec.ts @@ -0,0 +1,23 @@ +import { isDirectoryTransferItem } from './fileHandler' + +describe('isDirectoryTransferItem', () => { + it('detects directory transfer items', () => { + const item = { + webkitGetAsEntry: () => ({ isDirectory: true }), + } as DataTransferItem + + expect(isDirectoryTransferItem(item)).toBe(true) + }) + + it('ignores file transfer items', () => { + const item = { + webkitGetAsEntry: () => ({ isDirectory: false }), + } as DataTransferItem + + expect(isDirectoryTransferItem(item)).toBe(false) + }) + + it('ignores transfer items without entry support', () => { + expect(isDirectoryTransferItem({} as DataTransferItem)).toBe(false) + }) +}) diff --git a/src/cloud/lib/editor/plugins/fileHandler.ts b/src/cloud/lib/editor/plugins/fileHandler.ts index 63812c1367..d2ae1bce61 100644 --- a/src/cloud/lib/editor/plugins/fileHandler.ts +++ b/src/cloud/lib/editor/plugins/fileHandler.ts @@ -1,6 +1,10 @@ import { boostHubBaseUrl } from '../../consts' export type OnFileCallback = (file: File) => Promise +export type OnUnsupportedFileCallback = ( + file: File, + reason: 'directory' +) => void type FileNode = | { type: 'img'; url: string; alt?: string; title?: string } @@ -8,14 +12,31 @@ type FileNode = interface FileHandlerConfig { onFile: OnFileCallback + onUnsupportedFile?: OnUnsupportedFileCallback buildWidget?: (file: File) => HTMLElement lineClass?: string } +interface DataTransferItemEntryLike { + webkitGetAsEntry?: () => { + isDirectory: boolean + } | null +} + +export const isDirectoryTransferItem = (item: DataTransferItem | undefined) => { + if (item == null) { + return false + } + + const entry = (item as DataTransferItemEntryLike).webkitGetAsEntry?.() + return entry != null && entry.isDirectory +} + const attachFileHandlerToCodeMirrorEditor = ( editor: CodeMirror.Editor, { onFile, + onUnsupportedFile, buildWidget = buildDefaultUploadWidget, lineClass = 'file-loading', }: FileHandlerConfig @@ -49,6 +70,10 @@ const attachFileHandlerToCodeMirrorEditor = ( const pos = instance.coordsChar({ left: event.pageX, top: event.pageY }) const files = event.dataTransfer.files for (let i = 0; i < files.length; i++) { + if (isDirectoryTransferItem(event.dataTransfer.items[i])) { + onUnsupportedFile?.(files[i], 'directory') + continue + } await handler(i > 0 ? instance.getCursor() : pos, files[i]) } }