BookmarkFolderMenu.tsx 6.1 KB

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