BookmarkFolderMenu.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import React, { useCallback, useMemo, useState } from 'react';
  2. import { getCustomModifiers } from '@growi/ui/dist/utils';
  3. import { useTranslation } from 'next-i18next';
  4. import { DropdownItem, DropdownMenu, UncontrolledDropdown } from 'reactstrap';
  5. import { addBookmarkToFolder, toggleBookmark } from '~/client/util/bookmark-utils';
  6. import { toastError } from '~/client/util/toastr';
  7. import { useSWRMUTxCurrentUserBookmarks } from '~/stores/bookmark';
  8. import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
  9. import { useCurrentUser } from '~/stores/context';
  10. import { useSWRMUTxPageInfo } from '~/stores/page';
  11. import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
  12. type BookmarkFolderMenuProps = {
  13. isOpen: boolean,
  14. pageId: string,
  15. isBookmarked: boolean,
  16. onToggle?: () => void,
  17. onUnbookmark?: () => void,
  18. children?: React.ReactNode,
  19. }
  20. export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element => {
  21. const {
  22. isOpen, pageId, isBookmarked, onToggle, onUnbookmark, children,
  23. } = props;
  24. const { t } = useTranslation();
  25. const [selectedItem, setSelectedItem] = useState<string | null>(null);
  26. const { data: currentUser } = useCurrentUser();
  27. const { data: bookmarkFolders, mutate: mutateBookmarkFolders } = useSWRxBookmarkFolderAndChild(currentUser?._id);
  28. const { trigger: mutateCurrentUserBookmarks } = useSWRMUTxCurrentUserBookmarks();
  29. const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(pageId);
  30. const isBookmarkFolderExists = useMemo((): boolean => {
  31. return bookmarkFolders != null && bookmarkFolders.length > 0;
  32. }, [bookmarkFolders]);
  33. const toggleBookmarkHandler = useCallback(async() => {
  34. try {
  35. await toggleBookmark(pageId, isBookmarked);
  36. }
  37. catch (err) {
  38. toastError(err);
  39. }
  40. }, [isBookmarked, pageId]);
  41. const onUnbookmarkHandler = useCallback(async() => {
  42. if (onUnbookmark != null) {
  43. onUnbookmark();
  44. }
  45. await toggleBookmarkHandler();
  46. setSelectedItem(null);
  47. mutateCurrentUserBookmarks();
  48. mutateBookmarkFolders();
  49. mutatePageInfo();
  50. }, [onUnbookmark, toggleBookmarkHandler, mutateCurrentUserBookmarks, mutateBookmarkFolders, mutatePageInfo]);
  51. const toggleHandler = useCallback(async() => {
  52. // on close
  53. if (isOpen && bookmarkFolders != null) {
  54. bookmarkFolders.forEach((bookmarkFolder) => {
  55. bookmarkFolder.bookmarks.forEach((bookmark) => {
  56. if (bookmark.page._id === pageId) {
  57. setSelectedItem(bookmarkFolder._id);
  58. }
  59. });
  60. });
  61. }
  62. if (onToggle != null) {
  63. onToggle();
  64. }
  65. if (selectedItem == null) {
  66. setSelectedItem('root');
  67. }
  68. if (!isOpen && !isBookmarked) {
  69. try {
  70. await toggleBookmarkHandler();
  71. mutateCurrentUserBookmarks();
  72. mutatePageInfo();
  73. }
  74. catch (err) {
  75. toastError(err);
  76. }
  77. }
  78. },
  79. [isOpen, bookmarkFolders, onToggle, selectedItem, isBookmarked, pageId, toggleBookmarkHandler, mutateCurrentUserBookmarks, mutatePageInfo]);
  80. const onMenuItemClickHandler = useCallback(async(e, itemId: string) => {
  81. e.stopPropagation();
  82. setSelectedItem(itemId);
  83. try {
  84. await addBookmarkToFolder(pageId, itemId === 'root' ? null : itemId);
  85. mutateCurrentUserBookmarks();
  86. mutateBookmarkFolders();
  87. mutatePageInfo();
  88. }
  89. catch (err) {
  90. toastError(err);
  91. }
  92. }, [pageId, mutateCurrentUserBookmarks, mutateBookmarkFolders, mutatePageInfo]);
  93. const renderBookmarkMenuItem = () => {
  94. return (
  95. <>
  96. <DropdownItem
  97. toggle={false}
  98. onClick={onUnbookmarkHandler}
  99. className={'grw-bookmark-folder-menu-item text-danger'}
  100. >
  101. <i className="fa fa-bookmark"></i>{' '}
  102. <span className="mx-2">
  103. {t('bookmark_folder.cancel_bookmark')}
  104. </span>
  105. </DropdownItem>
  106. {isBookmarkFolderExists && (
  107. <>
  108. <DropdownItem divider />
  109. <div key="root">
  110. <div
  111. className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
  112. tabIndex={0}
  113. role="menuitem"
  114. onClick={e => onMenuItemClickHandler(e, 'root')}
  115. >
  116. <BookmarkFolderMenuItem
  117. itemId="root"
  118. itemName={t('bookmark_folder.root')}
  119. isSelected={selectedItem === 'root'}
  120. />
  121. </div>
  122. </div>
  123. {bookmarkFolders?.map(folder => (
  124. <React.Fragment key={`bookmark-folders-${folder._id}`}>
  125. <div
  126. className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
  127. style={{ paddingLeft: '40px' }}
  128. tabIndex={0}
  129. role="menuitem"
  130. onClick={e => onMenuItemClickHandler(e, folder._id)}
  131. >
  132. <BookmarkFolderMenuItem
  133. itemId={folder._id}
  134. itemName={folder.name}
  135. isSelected={selectedItem === folder._id}
  136. />
  137. </div>
  138. {folder.children?.map(child => (
  139. <div key={child._id}>
  140. <div
  141. className='dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0'
  142. style={{ paddingLeft: '60px' }}
  143. tabIndex={0}
  144. role="menuitem"
  145. onClick={e => onMenuItemClickHandler(e, child._id)}
  146. >
  147. <BookmarkFolderMenuItem
  148. itemId={child._id}
  149. itemName={child.name}
  150. isSelected={selectedItem === child._id}
  151. />
  152. </div>
  153. </div>
  154. ))}
  155. </React.Fragment>
  156. ))}
  157. </>
  158. )}
  159. </>
  160. );
  161. };
  162. return (
  163. <UncontrolledDropdown
  164. isOpen={isOpen}
  165. onToggle={toggleHandler}
  166. direction={isBookmarkFolderExists ? 'up' : 'down'}
  167. className='grw-bookmark-folder-dropdown'
  168. >
  169. {children}
  170. <DropdownMenu
  171. right
  172. persist
  173. positionFixed
  174. className='grw-bookmark-folder-menu'
  175. modifiers={getCustomModifiers(true)}
  176. >
  177. { renderBookmarkMenuItem() }
  178. </DropdownMenu>
  179. </UncontrolledDropdown>
  180. );
  181. };