BookmarkFolderMenu.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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 (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, currentPage?._id, toggleBookmarkHandler, mutateUserBookmarks, mutateBookmarkInfo, mutatePageInfo]);
  70. const onMenuItemClickHandler = useCallback(async(e, itemId: string) => {
  71. e.stopPropagation();
  72. setSelectedItem(itemId);
  73. try {
  74. if (currentPage != null) {
  75. await addBookmarkToFolder(currentPage._id, itemId === 'root' ? null : itemId);
  76. }
  77. mutateUserBookmarks();
  78. mutateBookmarkFolders();
  79. mutateBookmarkInfo();
  80. }
  81. catch (err) {
  82. toastError(err);
  83. }
  84. }, [mutateBookmarkFolders, currentPage, mutateBookmarkInfo, mutateUserBookmarks]);
  85. const renderBookmarkMenuItem = () => {
  86. return (
  87. <>
  88. <DropdownItem
  89. toggle={false}
  90. onClick={onUnbookmarkHandler}
  91. className={'grw-bookmark-folder-menu-item text-danger'}
  92. >
  93. <i className="fa fa-bookmark"></i>{' '}
  94. <span className="mx-2 ">
  95. {t('bookmark_folder.cancel_bookmark')}
  96. </span>
  97. </DropdownItem>
  98. {isBookmarkFolderExists && (
  99. <>
  100. <DropdownItem divider />
  101. <div key='root'>
  102. <div
  103. className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
  104. tabIndex={0}
  105. role="menuitem"
  106. onClick={e => onMenuItemClickHandler(e, 'root')}
  107. >
  108. <BookmarkFolderMenuItem
  109. itemId='root'
  110. itemName={t('bookmark_folder.root')}
  111. isSelected={selectedItem === 'root'}
  112. />
  113. </div>
  114. </div>
  115. {bookmarkFolders?.map(folder => (
  116. <>
  117. <div key={folder._id}>
  118. <div
  119. className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
  120. style={{ paddingLeft: '40px' }}
  121. tabIndex={0}
  122. role="menuitem"
  123. onClick={e => onMenuItemClickHandler(e, folder._id)}
  124. >
  125. <BookmarkFolderMenuItem
  126. itemId={folder._id}
  127. itemName={folder.name}
  128. isSelected={selectedItem === folder._id}
  129. />
  130. </div>
  131. </div>
  132. <>
  133. {folder.children?.map(child => (
  134. <div key={child._id}>
  135. <div
  136. className='dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0'
  137. style={{ paddingLeft: '60px' }}
  138. tabIndex={0}
  139. role="menuitem"
  140. onClick={e => onMenuItemClickHandler(e, child._id)}>
  141. <BookmarkFolderMenuItem
  142. itemId={child._id}
  143. itemName={child.name}
  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. };