import { FC, useCallback, useEffect, useState, useMemo, } from 'react'; import { useTranslation } from 'next-i18next'; import { useDrag, useDrop } from 'react-dnd'; import { DropdownToggle } from 'reactstrap'; import { apiv3Delete, apiv3Post, apiv3Put } from '~/client/util/apiv3-client'; import { toastError, toastSuccess } from '~/client/util/toastr'; import CountBadge from '~/components/Common/CountBadge'; import FolderIcon from '~/components/Icons/FolderIcon'; import TriangleIcon from '~/components/Icons/TriangleIcon'; import { BookmarkFolderItems } from '~/interfaces/bookmark-info'; import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page'; import { OnDeletedFunction } from '~/interfaces/ui'; import { useSWRBookmarkInfo } from '~/stores/bookmark'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import { usePageDeleteModal } from '~/stores/modal'; import { useSWRxCurrentPage } from '~/stores/page'; import BookmarkFolderItemControl from './BookmarkFolderItemControl'; import BookmarkFolderNameInput from './BookmarkFolderNameInput'; import BookmarkItem from './BookmarkItem'; import DeleteBookmarkFolderModal from './DeleteBookmarkFolderModal'; type BookmarkFolderItemProps = { bookmarkFolder: BookmarkFolderItems isOpen?: boolean level: number root: string } const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { const { bookmarkFolder, isOpen: _isOpen = false, level, root, } = props; const { t } = useTranslation(); const { name, _id: folderId, children, parent, bookmarks, } = bookmarkFolder; const [currentChildren, setCurrentChildren] = useState(); const [targetFolder, setTargetFolder] = useState(folderId); const [isOpen, setIsOpen] = useState(_isOpen); const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(targetFolder); const { mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild(parent); const [isRenameAction, setIsRenameAction] = useState(false); const [isCreateAction, setIsCreateAction] = useState(false); const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState(false); const { data: currentPage } = useSWRxCurrentPage(); const { mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id); const { open: openDeleteModal } = usePageDeleteModal(); const childCount = useMemo((): number => { if (currentChildren != null && currentChildren.length > children.length) { return currentChildren.length; } return children.length; }, [children.length, currentChildren]); useEffect(() => { if (childBookmarkFolderData != null) { mutateChildBookmarkData(); setCurrentChildren(childBookmarkFolderData); } }, [childBookmarkFolderData, mutateChildBookmarkData]); const hasChildren = useCallback((): boolean => { if (currentChildren != null && currentChildren.length > children.length) { return currentChildren.length > 0; } return children.length > 0; }, [children.length, currentChildren]); const loadChildFolder = useCallback(async() => { setIsOpen(!isOpen); setTargetFolder(folderId); }, [folderId, isOpen]); const loadParent = useCallback(async() => { if (!isRenameAction) { if (parent != null) { await mutateParentBookmarkFolder(); } // Reload root folder structure setTargetFolder(null); } else { await mutateParentBookmarkFolder(); } }, [isRenameAction, mutateParentBookmarkFolder, parent]); // Rename for bookmark folder handler const onPressEnterHandlerForRename = useCallback(async(folderName: string) => { try { await apiv3Put('/bookmark-folder', { bookmarkFolderId: folderId, name: folderName, parent }); loadParent(); setIsRenameAction(false); toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' })); } catch (err) { toastError(err); } }, [folderId, loadParent, parent, t]); // Create new folder / subfolder handler const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { try { await apiv3Post('/bookmark-folder', { name: folderName, parent: targetFolder }); setIsOpen(true); setIsCreateAction(false); mutateChildBookmarkData(); toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' })); } catch (err) { toastError(err); } }, [mutateChildBookmarkData, t, targetFolder]); // Delete Fodler handler const onClickDeleteButtonHandler = useCallback(async() => { try { await apiv3Delete(`/bookmark-folder/${folderId}`); setIsDeleteFolderModalShown(false); loadParent(); mutateBookmarkInfo(); toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' })); } catch (err) { toastError(err); } }, [folderId, loadParent, mutateBookmarkInfo, t]); const onClickPlusButton = useCallback(async(e) => { e.stopPropagation(); if (!isOpen && hasChildren()) { setIsOpen(true); } setIsCreateAction(true); }, [hasChildren, isOpen]); const onClickDeleteBookmarkHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => { const pageDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { if (typeof pathOrPathsToDelete !== 'string') { return; } const path = pathOrPathsToDelete; if (isCompletely) { toastSuccess(t('deleted_pages_completely', { path })); } else { toastSuccess(t('deleted_pages', { path })); } mutateParentBookmarkFolder(); mutateBookmarkInfo(); }; openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); }, [mutateBookmarkInfo, mutateParentBookmarkFolder, openDeleteModal, t]); const onUnbookmarkHandler = useCallback(() => { mutateParentBookmarkFolder(); mutateBookmarkInfo(); }, [mutateBookmarkInfo, mutateParentBookmarkFolder]); const [, bookmarkFolderDragRef] = useDrag({ type: 'FOLDER', item: props, end: (item, monitor) => { const dropResult = monitor.getDropResult(); if (dropResult != null) { mutateParentBookmarkFolder(); } }, collect: monitor => ({ isDragging: monitor.isDragging(), canDrag: monitor.canDrag(), }), }); const folderItemDropHandler = async(item: BookmarkFolderItemProps) => { try { await apiv3Put('/bookmark-folder', { bookmarkFolderId: item.bookmarkFolder._id, name: item.bookmarkFolder.name, parent: bookmarkFolder._id }); await mutateChildBookmarkData(); toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder') })); } catch (err) { toastError(err); } }; const bookmarkItemDropHandler = useCallback(async(item: IPageHasId) => { try { await apiv3Post('/bookmark-folder/add-boookmark-to-folder', { pageId: item._id, folderId: bookmarkFolder._id }); mutateParentBookmarkFolder(); toastSuccess('Bookmark added to bookmark folder successfully'); } catch (err) { toastError(err); } }, [bookmarkFolder._id, mutateParentBookmarkFolder]); const isDroppable = (item: BookmarkFolderItemProps, targetRoot: string, targetLevel: number): boolean => { if (item.bookmarkFolder.parent === bookmarkFolder._id || item.bookmarkFolder._id === bookmarkFolder._id) { return false; } if (item.root === targetRoot) { if (item.level < targetLevel) { return false; } } return true; }; const [, bookmarkFolderDropRef] = useDrop(() => ({ accept: 'FOLDER', drop: folderItemDropHandler, canDrop: (item) => { // Implement isDropable function & improve return isDroppable(item, root, level); }, collect: monitor => ({ isOver: monitor.isOver(), }), })); const [, bookmarkItemDropRef] = useDrop(() => ({ accept: 'BOOKMARK', drop: bookmarkItemDropHandler, collect: monitor => ({ isOver: monitor.isOver(), }), })); const renderChildFolder = () => { return isOpen && currentChildren?.map((childFolder) => { return (
); }); }; const renderBookmarkItem = () => { return isOpen && bookmarks?.map((bookmark) => { return ( ); }); }; const onClickRenameHandler = useCallback(() => { setIsRenameAction(true); }, []); const onClickDeleteHandler = useCallback(() => { setIsDeleteFolderModalShown(true); }, []); const onDeleteFolderModalClose = useCallback(() => { setIsDeleteFolderModalShown(false); }, []); return (
  • { bookmarkFolderDragRef(c); bookmarkFolderDropRef(c); bookmarkItemDropRef(c) }} className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center" onClick={loadChildFolder} >
    {hasChildren() && ( )}
    {
    } {isRenameAction ? ( setIsRenameAction(false)} onPressEnter={onPressEnterHandlerForRename} value={name} /> ) : ( <>

    {name}

    {hasChildren() && (
    )} ) }
    e.stopPropagation()}>
  • {isCreateAction && (
    setIsCreateAction(false)} onPressEnter={onPressEnterHandlerForCreate} />
    )} { renderChildFolder() } { renderBookmarkItem() }
    ); }; export default BookmarkFolderItem;