BookmarkItem.tsx 5.5 KB

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