import React, { useCallback, useMemo, useState, type JSX, } from 'react'; import nodePath from 'path'; import type { IPageHasId, IPageInfoExt, IPageToDeleteWithMeta } from '@growi/core'; import { getIdStringForRef } from '@growi/core'; import { DevidedPagePath } from '@growi/core/dist/models'; import { pathUtils } from '@growi/core/dist/utils'; import { useRouter } from 'next/router'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import { bookmark, unbookmark, unlink } from '~/client/services/page-operation'; import { addBookmarkToFolder, renamePage } from '~/client/util/bookmark-utils'; import { toastError, toastSuccess } from '~/client/util/toastr'; import type { BookmarkFolderItems, DragItemDataType } from '~/interfaces/bookmark-info'; import { DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info'; import { useFetchCurrentPage } from '~/states/page'; import { usePutBackPageModalActions } from '~/states/ui/modal/put-back-page'; import { mutateAllPageInfo, useSWRxPageInfo } from '~/stores/page'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; import { PageListItemS } from '../PageList/PageListItemS'; import { BookmarkItemRenameInput } from './BookmarkItemRenameInput'; import { BookmarkMoveToRootBtn } from './BookmarkMoveToRootBtn'; import { DragAndDropWrapper } from './DragAndDropWrapper'; type Props = { isReadOnlyUser: boolean isOperable: boolean, bookmarkedPage: IPageHasId | null, level: number, parentFolder: BookmarkFolderItems | null, canMoveToRoot: boolean, onClickDeleteMenuItemHandler: (pageToDelete: IPageToDeleteWithMeta) => void, bookmarkFolderTreeMutation: () => void, } export const BookmarkItem = (props: Props): JSX.Element => { const BASE_FOLDER_PADDING = 15; const BASE_BOOKMARK_PADDING = 16; const { t } = useTranslation(); const router = useRouter(); const { isReadOnlyUser, isOperable, bookmarkedPage, onClickDeleteMenuItemHandler, parentFolder, level, canMoveToRoot, bookmarkFolderTreeMutation, } = props; const { open: openPutBackPageModal } = usePutBackPageModalActions(); const [isRenameInputShown, setRenameInputShown] = useState(false); const { data: pageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(bookmarkedPage?._id); const { fetchCurrentPage } = useFetchCurrentPage(); const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level)); const dragItem: Partial = { ...bookmarkedPage, parentFolder, }; const bookmarkedPageId = bookmarkedPage?._id; const bookmarkedPagePath = bookmarkedPage?.path; const bookmarkedPageRevision = bookmarkedPage?.revision; const onClickMoveToRootHandler = useCallback(async() => { if (bookmarkedPageId == null) return; try { await addBookmarkToFolder(bookmarkedPageId, null); bookmarkFolderTreeMutation(); } catch (err) { toastError(err); } }, [bookmarkFolderTreeMutation, bookmarkedPageId]); const bookmarkMenuItemClickHandler = useCallback(async(pageId: string, shouldBookmark: boolean) => { if (shouldBookmark) { await bookmark(pageId); } else { await unbookmark(pageId); } bookmarkFolderTreeMutation(); mutatePageInfo(); }, [bookmarkFolderTreeMutation, mutatePageInfo]); const renameMenuItemClickHandler = useCallback(() => { setRenameInputShown(true); }, []); const cancel = useCallback(() => { setRenameInputShown(false); }, []); const rename = useCallback(async(inputText: string) => { if (bookmarkedPageId == null) return; if (inputText.trim() === '') { return cancel(); } const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(bookmarkedPagePath ?? '')); const newPagePath = nodePath.resolve(parentPath, inputText.trim()); if (newPagePath === bookmarkedPagePath) { setRenameInputShown(false); return; } try { setRenameInputShown(false); await renamePage(bookmarkedPageId, bookmarkedPageRevision, newPagePath); bookmarkFolderTreeMutation(); mutatePageInfo(); } catch (err) { setRenameInputShown(true); toastError(err); } }, [bookmarkedPageId, bookmarkedPagePath, bookmarkedPageRevision, cancel, bookmarkFolderTreeMutation, mutatePageInfo]); const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoExt | undefined): Promise => { if (bookmarkedPageId == null) return; if (bookmarkedPageId == null || bookmarkedPagePath == null) { throw Error('_id and path must not be null.'); } const pageToDelete: IPageToDeleteWithMeta = { data: { _id: bookmarkedPageId, revision: bookmarkedPageRevision == null ? null : getIdStringForRef(bookmarkedPageRevision), path: bookmarkedPagePath, }, meta: pageInfo, }; onClickDeleteMenuItemHandler(pageToDelete); }, [bookmarkedPageId, bookmarkedPagePath, bookmarkedPageRevision, onClickDeleteMenuItemHandler]); const putBackClickHandler = useCallback(() => { if (bookmarkedPage == null) return; const { _id: pageId, path } = bookmarkedPage; const putBackedHandler = async() => { try { await unlink(path); mutateAllPageInfo(); bookmarkFolderTreeMutation(); router.push(`/${pageId}`); fetchCurrentPage({ force: true }); toastSuccess(t('page_has_been_reverted', { path })); } catch (err) { toastError(err); } }; openPutBackPageModal({ pageId, path }, { onPutBacked: putBackedHandler }); }, [bookmarkedPage, openPutBackPageModal, bookmarkFolderTreeMutation, router, fetchCurrentPage, t]); const { pageTitle, formerPagePath, isFormerRoot, bookmarkItemId, } = useMemo(() => { const bookmarkItemId = `bookmark-item-${bookmarkedPageId}`; if (bookmarkedPagePath == null) { return { pageTitle: '', formerPagePath: '', isFormerRoot: false, bookmarkItemId, }; } const dPagePath = new DevidedPagePath(bookmarkedPagePath, false, true); return { pageTitle: dPagePath.latter, formerPagePath: dPagePath.former, isFormerRoot: dPagePath.isFormerRoot, bookmarkItemId, }; }, [bookmarkedPagePath, bookmarkedPageId]); if (bookmarkedPage == null) { return <>; } return (
  • { isRenameInputShown ? ( { setRenameInputShown(false) }} /> ) : }
    : undefined} > more_vert
    {isFormerRoot ? '/' : `${formerPagePath}/`}
  • ); };