import { FC, useCallback, useState, } from 'react'; import { DropdownToggle } from 'reactstrap'; import { addBookmarkToFolder, addNewFolder, hasChildren, updateBookmarkFolder, } from '~/client/util/bookmark-utils'; import { toastError } from '~/client/util/toastr'; import { FolderIcon } from '~/components/Icons/FolderIcon'; import { TriangleIcon } from '~/components/Icons/TriangleIcon'; import { BookmarkFolderItems, DragItemDataType, DragItemType, DRAG_ITEM_TYPE, } from '~/interfaces/bookmark-info'; import { IPageToDeleteWithMeta } from '~/interfaces/page'; import { onDeletedBookmarkFolderFunction } from '~/interfaces/ui'; import { useBookmarkFolderDeleteModal } from '~/stores/modal'; import { BookmarkFolderItemControl } from './BookmarkFolderItemControl'; import { BookmarkFolderNameInput } from './BookmarkFolderNameInput'; import { BookmarkItem } from './BookmarkItem'; import { DragAndDropWrapper } from './DragAndDropWrapper'; type BookmarkFolderItemProps = { isReadOnlyUser: boolean bookmarkFolder: BookmarkFolderItems isOpen?: boolean isOperable: boolean, level: number root: string isUserHomePage?: boolean onClickDeleteBookmarkHandler: (pageToDelete: IPageToDeleteWithMeta) => void bookmarkFolderTreeMutation: () => void } export const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { const BASE_FOLDER_PADDING = 15; const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK]; const { isReadOnlyUser, bookmarkFolder, isOpen: _isOpen = false, isOperable, level, root, isUserHomePage, onClickDeleteBookmarkHandler, bookmarkFolderTreeMutation, } = props; const { name, _id: folderId, children, parent, bookmarks, } = bookmarkFolder; const [targetFolder, setTargetFolder] = useState(folderId); const [isOpen, setIsOpen] = useState(_isOpen); const [isRenameAction, setIsRenameAction] = useState(false); const [isCreateAction, setIsCreateAction] = useState(false); const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal(); const childrenExists = hasChildren(children); const paddingLeft = BASE_FOLDER_PADDING * level; const loadChildFolder = useCallback(async() => { setIsOpen(!isOpen); setTargetFolder(folderId); }, [folderId, isOpen]); // Rename for bookmark folder handler const onPressEnterHandlerForRename = useCallback(async(folderName: string) => { try { // TODO: do not use any type await updateBookmarkFolder(folderId, folderName, parent as any); bookmarkFolderTreeMutation(); setIsRenameAction(false); } catch (err) { toastError(err); } }, [bookmarkFolderTreeMutation, folderId, parent]); // Create new folder / subfolder handler const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { try { await addNewFolder(folderName, targetFolder); setIsOpen(true); setIsCreateAction(false); bookmarkFolderTreeMutation(); } catch (err) { toastError(err); } }, [bookmarkFolderTreeMutation, targetFolder]); const onClickPlusButton = useCallback(async(e) => { e.stopPropagation(); if (!isOpen && childrenExists) { setIsOpen(true); } setIsCreateAction(true); }, [childrenExists, isOpen]); const itemDropHandler = async(item: DragItemDataType, dragItemType: string | symbol | null) => { if (dragItemType === DRAG_ITEM_TYPE.FOLDER) { try { if (item.bookmarkFolder != null) { await updateBookmarkFolder(item.bookmarkFolder._id, item.bookmarkFolder.name, bookmarkFolder._id); bookmarkFolderTreeMutation(); } } catch (err) { toastError(err); } } else { try { if (item != null) { await addBookmarkToFolder(item._id, bookmarkFolder._id); bookmarkFolderTreeMutation(); } } catch (err) { toastError(err); } } }; const isDropable = (item: DragItemDataType, type: string | null| symbol): boolean => { if (type === DRAG_ITEM_TYPE.FOLDER) { if (item.bookmarkFolder.parent === bookmarkFolder._id || item.bookmarkFolder._id === bookmarkFolder._id) { return false; } // Maximum folder hierarchy of 2 levels // If the drop source folder has child folders, the drop source folder cannot be moved because the drop source folder hierarchy is already 2. // If the destination folder has a parent, the source folder cannot be moved because the destination folder hierarchy is already 2. if (item.bookmarkFolder.children.length !== 0 || bookmarkFolder.parent != null) { return false; } return item.root !== root || item.level >= level; } if (item.parentFolder != null && item.parentFolder._id === bookmarkFolder._id) { return false; } return true; }; const renderChildFolder = () => { return isOpen && children?.map((childFolder) => { return (
); }); }; const renderBookmarkItem = () => { return isOpen && bookmarks?.map((bookmark) => { return ( ); }); }; const onClickRenameHandler = useCallback(() => { setIsRenameAction(true); }, []); const onClickDeleteHandler = useCallback(() => { const bookmarkFolderDeleteHandler: onDeletedBookmarkFolderFunction = (folderId) => { if (typeof folderId !== 'string') { return; } bookmarkFolderTreeMutation(); }; if (bookmarkFolder == null) { return; } openDeleteBookmarkFolderModal(bookmarkFolder, { onDeleted: bookmarkFolderDeleteHandler }); }, [bookmarkFolder, bookmarkFolderTreeMutation, openDeleteBookmarkFolderModal]); const onClickMoveToRootHandlerForBookmarkFolderItemControl = useCallback(async() => { try { await updateBookmarkFolder(bookmarkFolder._id, bookmarkFolder.name, null); bookmarkFolderTreeMutation(); } catch (err) { toastError(err); } }, [bookmarkFolder._id, bookmarkFolder.name, bookmarkFolderTreeMutation]); return (
  • {childrenExists && ( )}
    {
    } {isRenameAction ? ( setIsRenameAction(false)} onPressEnter={onPressEnterHandlerForRename} value={name} /> ) : ( <>

    {name}

    )} { isOperable && (
    e.stopPropagation()}>
    {/* Maximum folder hierarchy of 2 levels */} {!(bookmarkFolder.parent != null) && ( )}
    )}
  • {isCreateAction && (
    setIsCreateAction(false)} onPressEnter={onPressEnterHandlerForCreate} />
    )} { renderChildFolder() } { renderBookmarkItem() }
    ); };