BookmarkFolderMenu.tsx 6.5 KB

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