BookmarkItem.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import React, { useCallback, useState } from 'react';
  2. import nodePath from 'path';
  3. import { DevidedPagePath, pathUtils } from '@growi/core';
  4. import { useTranslation } from 'react-i18next';
  5. import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
  6. import { bookmark, unbookmark } from '~/client/services/page-operation';
  7. import { addBookmarkToFolder, renamePage } from '~/client/util/bookmark-utils';
  8. import { ValidationTarget } from '~/client/util/input-validator';
  9. import { toastError } from '~/client/util/toastr';
  10. import { BookmarkFolderItems, DragItemDataType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
  11. import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
  12. import { useSWRxPageInfo } from '~/stores/page';
  13. import ClosableTextInput from '../Common/ClosableTextInput';
  14. import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
  15. import { PageListItemS } from '../PageList/PageListItemS';
  16. import { BookmarkMoveToRootBtn } from './BookmarkMoveToRootBtn';
  17. import { DragAndDropWrapper } from './DragAndDropWrapper';
  18. type Props = {
  19. isReadOnlyUser: boolean
  20. isOperable: boolean,
  21. bookmarkedPage: IPageHasId,
  22. level: number,
  23. parentFolder: BookmarkFolderItems | null,
  24. canMoveToRoot: boolean,
  25. onClickDeleteMenuItemHandler: (pageToDelete: IPageToDeleteWithMeta) => void,
  26. bookmarkFolderTreeMutation: () => void
  27. }
  28. export const BookmarkItem = (props: Props): JSX.Element => {
  29. const BASE_FOLDER_PADDING = 15;
  30. const BASE_BOOKMARK_PADDING = 20;
  31. const { t } = useTranslation();
  32. const {
  33. isReadOnlyUser, isOperable, bookmarkedPage, onClickDeleteMenuItemHandler,
  34. parentFolder, level, canMoveToRoot, bookmarkFolderTreeMutation,
  35. } = props;
  36. const [isRenameInputShown, setRenameInputShown] = useState(false);
  37. const { data: pageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(bookmarkedPage._id);
  38. const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
  39. const { latter: pageTitle, former: formerPagePath } = dPagePath;
  40. const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
  41. const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level + 1));
  42. const dragItem: Partial<DragItemDataType> = {
  43. ...bookmarkedPage, parentFolder,
  44. };
  45. const onClickMoveToRootHandler = useCallback(async() => {
  46. try {
  47. await addBookmarkToFolder(bookmarkedPage._id, null);
  48. bookmarkFolderTreeMutation();
  49. }
  50. catch (err) {
  51. toastError(err);
  52. }
  53. }, [bookmarkFolderTreeMutation, bookmarkedPage._id]);
  54. const bookmarkMenuItemClickHandler = useCallback(async(pageId: string, shouldBookmark: boolean) => {
  55. if (shouldBookmark) {
  56. await bookmark(pageId);
  57. }
  58. else {
  59. await unbookmark(pageId);
  60. }
  61. bookmarkFolderTreeMutation();
  62. mutatePageInfo();
  63. }, [bookmarkFolderTreeMutation, mutatePageInfo]);
  64. const renameMenuItemClickHandler = useCallback(() => {
  65. setRenameInputShown(true);
  66. }, []);
  67. const pressEnterForRenameHandler = useCallback(async(inputText: string) => {
  68. const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(bookmarkedPage.path ?? ''));
  69. const newPagePath = nodePath.resolve(parentPath, inputText);
  70. if (newPagePath === bookmarkedPage.path) {
  71. setRenameInputShown(false);
  72. return;
  73. }
  74. try {
  75. setRenameInputShown(false);
  76. await renamePage(bookmarkedPage._id, bookmarkedPage.revision, newPagePath);
  77. bookmarkFolderTreeMutation();
  78. mutatePageInfo();
  79. }
  80. catch (err) {
  81. setRenameInputShown(true);
  82. toastError(err);
  83. }
  84. }, [bookmarkedPage.path, bookmarkedPage._id, bookmarkedPage.revision, bookmarkFolderTreeMutation, mutatePageInfo]);
  85. const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise<void> => {
  86. if (bookmarkedPage._id == null || bookmarkedPage.path == null) {
  87. throw Error('_id and path must not be null.');
  88. }
  89. const pageToDelete: IPageToDeleteWithMeta = {
  90. data: {
  91. _id: bookmarkedPage._id,
  92. revision: bookmarkedPage.revision as string,
  93. path: bookmarkedPage.path,
  94. },
  95. meta: pageInfo,
  96. };
  97. onClickDeleteMenuItemHandler(pageToDelete);
  98. }, [bookmarkedPage._id, bookmarkedPage.path, bookmarkedPage.revision, onClickDeleteMenuItemHandler]);
  99. return (
  100. <DragAndDropWrapper
  101. item={dragItem}
  102. type={[DRAG_ITEM_TYPE.BOOKMARK]}
  103. useDragMode={isOperable}
  104. >
  105. <li
  106. className="grw-bookmark-item-list list-group-item list-group-item-action border-0 py-0 mr-auto d-flex align-items-center"
  107. key={bookmarkedPage._id}
  108. id={bookmarkItemId}
  109. style={{ paddingLeft }}
  110. >
  111. { isRenameInputShown ? (
  112. <ClosableTextInput
  113. value={nodePath.basename(bookmarkedPage.path ?? '')}
  114. placeholder={t('Input page name')}
  115. onClickOutside={() => { setRenameInputShown(false) }}
  116. onPressEnter={pressEnterForRenameHandler}
  117. validationTarget={ValidationTarget.PAGE}
  118. />
  119. ) : <PageListItemS page={bookmarkedPage} pageTitle={pageTitle} isNarrowView/>}
  120. <div className='grw-foldertree-control'>
  121. <PageItemControl
  122. pageId={bookmarkedPage._id}
  123. isEnableActions
  124. isReadOnlyUser={isReadOnlyUser}
  125. pageInfo={pageInfo}
  126. forceHideMenuItems={[MenuItemType.DUPLICATE]}
  127. onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
  128. onClickRenameMenuItem={renameMenuItemClickHandler}
  129. onClickDeleteMenuItem={deleteMenuItemClickHandler}
  130. additionalMenuItemOnTopRenderer={canMoveToRoot && isOperable
  131. ? () => <BookmarkMoveToRootBtn pageId={bookmarkedPage._id} onClickMoveToRootHandler={onClickMoveToRootHandler}/>
  132. : undefined}
  133. >
  134. <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
  135. <i className="icon-options fa fa-rotate-90 p-1"></i>
  136. </DropdownToggle>
  137. </PageItemControl>
  138. </div>
  139. <UncontrolledTooltip
  140. modifiers={{ preventOverflow: { boundariesElement: 'window' } }}
  141. autohide={false}
  142. placement="right"
  143. target={bookmarkItemId}
  144. fade={false}
  145. >
  146. {formerPagePath !== null ? `${formerPagePath}/` : '/'}
  147. </UncontrolledTooltip>
  148. </li>
  149. </DragAndDropWrapper>
  150. );
  151. };