import React, { useCallback, useState, useMemo } from 'react'; import nodePath from 'path'; import { IPageInfoAll, IPageToDeleteWithMeta, pathUtils, } from '@growi/core'; import { useTranslation } from 'next-i18next'; import { DropdownItem, DropdownToggle } from 'reactstrap'; import { unbookmark } from '~/client/services/page-operation'; import { apiv3Put } from '~/client/util/apiv3-client'; import { addBookmarkToFolder } from '~/client/util/bookmark-utils'; import { ValidationTarget } from '~/client/util/input-validator'; import { toastError, toastSuccess } from '~/client/util/toastr'; import { IPageHasId } from '~/interfaces/page'; import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import loggerFactory from '~/utils/logger'; import ClosableTextInput from '../Common/ClosableTextInput'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; import { PageListItemS } from './PageListItemS'; const logger = loggerFactory('growi:BookmarkList'); type Props = { page: IPageHasId onRenamed: () => void onUnbookmarked: () => void onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void } export const BookmarkList = (props:Props): JSX.Element => { const { page, onRenamed, onUnbookmarked, onClickDeleteMenuItem, } = props; const { t } = useTranslation(); const [isRenameInputShown, setIsRenameInputShown] = useState(false); const pageId = page._id; const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks(); const isMoveToRoot = useMemo(() => { return !userBookmarks?.map(userBookmark => userBookmark._id).includes(pageId); }, [pageId, userBookmarks]); const moveToRootClickedHandler = useCallback(async() => { try { await addBookmarkToFolder(pageId, null); await mutateUserBookmarks(); } catch (err) { toastError(err); } }, [mutateUserBookmarks, pageId]); const additionalMenuItemOnTopRenderer = useMemo(() => { return ( {t('bookmark_folder.move_to_root')} ); }, [moveToRootClickedHandler, t]); const bookmarkMenuItemClickHandler = useCallback(async() => { await unbookmark(page._id); onUnbookmarked(); }, [page._id, onUnbookmarked]); const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { if (page._id == null || page.path == null) { throw Error('_id and path must not be null.'); } const pageToDelete: IPageToDeleteWithMeta = { data: { _id: page._id, revision: page.revision as string, path: page.path, }, meta: pageInfo, }; onClickDeleteMenuItem(pageToDelete); }, [onClickDeleteMenuItem, page]); const pressEnterForRenameHandler = useCallback(async(inputText: string) => { const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); const newPagePath = nodePath.resolve(parentPath, inputText); if (newPagePath === page.path) { setIsRenameInputShown(false); return; } try { setIsRenameInputShown(false); await apiv3Put('/pages/rename', { pageId: page._id, revisionId: page.revision, newPagePath, }); onRenamed(); toastSuccess(t('renamed_pages', { path: page.path })); } catch (err) { setIsRenameInputShown(true); logger.error('failed to fetch data', err); toastError(err); } }, [onRenamed, page, t]); return (
  • { isRenameInputShown ? ( { setIsRenameInputShown(false) }} onPressEnter={pressEnterForRenameHandler} validationTarget={ValidationTarget.PAGE} /> ) : ( )} setIsRenameInputShown(true)} onClickDeleteMenuItem={deleteMenuItemClickHandler} additionalMenuItemOnTopRenderer={isMoveToRoot ? (() => additionalMenuItemOnTopRenderer) : undefined} >
  • ); };