From 0629f02b376296310eb39dbfd8bd417a0c1176b6 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 14 Jul 2022 17:57:02 +0800 Subject: [PATCH 001/826] Add bookmark item on the sidebar https://youtrack.weseek.co.jp/issue/GW-7831 - Add bookmarks to SidebarContentsType - Add bookmarks button to sidebar - Add bookmark item to sidebar content - Create Bookmarks component (empty item but has a title) --- .../app/src/components/Sidebar/Bookmarks.tsx | 18 ++++++++++++++++++ .../src/components/Sidebar/SidebarContents.tsx | 7 ++++++- .../app/src/components/Sidebar/SidebarNav.tsx | 1 + packages/app/src/interfaces/ui.ts | 1 + 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/components/Sidebar/Bookmarks.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx new file mode 100644 index 00000000000..b9638a79959 --- /dev/null +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -0,0 +1,18 @@ +import React, { FC } from 'react'; + +import { useTranslation } from 'react-i18next'; + +const Bookmarks: FC = () => { + const { t } = useTranslation(''); + + // TODO Add bookmarks item + + return ( +
+

{t('Bookmarks')}

+
+ ); + +}; + +export default Bookmarks; diff --git a/packages/app/src/components/Sidebar/SidebarContents.tsx b/packages/app/src/components/Sidebar/SidebarContents.tsx index 5053efe0085..7feb93d1197 100644 --- a/packages/app/src/components/Sidebar/SidebarContents.tsx +++ b/packages/app/src/components/Sidebar/SidebarContents.tsx @@ -2,9 +2,11 @@ import React, { FC } from 'react'; import { SidebarContentsType } from '~/interfaces/ui'; import { useCurrentSidebarContents } from '~/stores/ui'; -import RecentChanges from './RecentChanges'; + +import Bookmarks from './Bookmarks'; import CustomSidebar from './CustomSidebar'; import PageTree from './PageTree'; +import RecentChanges from './RecentChanges'; import Tag from './Tag'; type Props = { @@ -24,6 +26,9 @@ const SidebarContents: FC = (props: Props) => { case SidebarContentsType.TAG: Contents = Tag; break; + case SidebarContentsType.BOOKMARKS: + Contents = Bookmarks; + break; default: Contents = PageTree; } diff --git a/packages/app/src/components/Sidebar/SidebarNav.tsx b/packages/app/src/components/Sidebar/SidebarNav.tsx index 11f6a4fda7d..707d99cafe3 100644 --- a/packages/app/src/components/Sidebar/SidebarNav.tsx +++ b/packages/app/src/components/Sidebar/SidebarNav.tsx @@ -89,6 +89,7 @@ const SidebarNav: FC = (props: Props) => { {/* */} {/* eslint-enable max-len */} +
{isAdmin && } diff --git a/packages/app/src/interfaces/ui.ts b/packages/app/src/interfaces/ui.ts index 6546e1e62e5..255241e17d4 100644 --- a/packages/app/src/interfaces/ui.ts +++ b/packages/app/src/interfaces/ui.ts @@ -5,6 +5,7 @@ export const SidebarContentsType = { RECENT: 'recent', TREE: 'tree', TAG: 'tag', + BOOKMARKS: 'bookmarks', } as const; export const AllSidebarContentsType = Object.values(SidebarContentsType); export type SidebarContentsType = typeof SidebarContentsType[keyof typeof SidebarContentsType]; From 563e02b0619a78f7307c1b34b0b4cdf58592b48e Mon Sep 17 00:00:00 2001 From: mudana Date: Fri, 15 Jul 2022 18:13:20 +0800 Subject: [PATCH 002/826] Show bookmarks list on the sidebar https://youtrack.weseek.co.jp/issue/GW-7832 - Add offset option to user's bookmarks route - Implement SWRInfinite to fetch user's bookmarks list - Modify useCurrentUser return type to IUserHasId - Implement Infinite scroll to bookmarks list on side bar - Add styling for bookmark list --- .../app/src/components/Sidebar/Bookmarks.tsx | 66 +++++++++++++++++-- .../app/src/server/routes/apiv3/bookmarks.js | 2 + packages/app/src/stores/bookmark.ts | 15 +++++ packages/app/src/stores/context.tsx | 6 +- .../app/src/styles/theme/_apply-colors.scss | 13 ++++ 5 files changed, 92 insertions(+), 10 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index b9638a79959..3757618dccb 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,16 +1,68 @@ -import React, { FC } from 'react'; +import React from 'react'; + +import { DevidedPagePath } from '@growi/core'; +import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -const Bookmarks: FC = () => { - const { t } = useTranslation(''); +import LinkedPagePath from '~/models/linked-page-path'; +import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; +import { useCurrentUser } from '~/stores/context'; + +import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; + +import InfiniteScroll from './InfiniteScroll'; + + +const BookmarksItem = ({ data }) => { + const { page } = data; + const dPagePath = new DevidedPagePath(page.path, false, true); + const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); + + return ( +
  • +
    +
    + +
    +
    +
  • + ); + +}; - // TODO Add bookmarks item +BookmarksItem.propTypes = { + data: PropTypes.any, +}; + +const Bookmarks = () : JSX.Element => { + const { t } = useTranslation(''); + const { data: currentUser } = useCurrentUser(); + const swr = useSWRInifiniteBookmarkedPage(currentUser?._id); + const isEmpty = swr.data?.[0].docs.length === 0; + const isReachingEnd = isEmpty || (swr.data && swr.data[0].nextPage == null); return ( -
    -

    {t('Bookmarks')}

    -
    + <> +
    +

    {t('Bookmarks')}

    +
    +
    + {isEmpty ? t('No bookmarks yet') : ( +
      + + {paginationResult => paginationResult?.docs.map(data => ( + + )) + } + +
    + )} +
    + ); }; diff --git a/packages/app/src/server/routes/apiv3/bookmarks.js b/packages/app/src/server/routes/apiv3/bookmarks.js index 39df6d67d8b..a895afceefc 100644 --- a/packages/app/src/server/routes/apiv3/bookmarks.js +++ b/packages/app/src/server/routes/apiv3/bookmarks.js @@ -201,6 +201,7 @@ module.exports = (crowi) => { const { userId } = req.params; const page = req.query.page; const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30; + const offset = page > 0 ? (page - 1) * limit : page; if (userId == null) { return res.apiv3Err('User id is not found or forbidden', 400); @@ -222,6 +223,7 @@ module.exports = (crowi) => { model: 'User', }, }, + offset, page, limit, }, diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 920729237b1..84d328e07b5 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,5 +1,6 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; +import useSWRInfinite, { SWRInfiniteResponse } from 'swr/infinite'; import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -17,3 +18,17 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon }), ); }; + +export const useSWRInifiniteBookmarkedPage = (userId: string | null | undefined) : SWRInfiniteResponse => { + const getKey = (page: number) => { + return userId != null ? `/bookmarks/${userId}/?page=${page + 1}` : null; + }; + return useSWRInfinite( + getKey, + (endpoint: string) => apiv3Get<{ paginationResult }>(endpoint).then(response => response.data?.paginationResult), + { + revalidateFirstPage: false, + revalidateAll: false, + }, + ); +}; diff --git a/packages/app/src/stores/context.tsx b/packages/app/src/stores/context.tsx index 49ff47e0765..337f0aacb3e 100644 --- a/packages/app/src/stores/context.tsx +++ b/packages/app/src/stores/context.tsx @@ -7,7 +7,7 @@ import { SupportedActionType } from '~/interfaces/activity'; import { GrowiRendererConfig } from '~/interfaces/services/renderer'; import { TargetAndAncestors } from '../interfaces/page-listing-results'; -import { IUser } from '../interfaces/user'; +import { IUser, IUserHasId } from '../interfaces/user'; import { useStaticSWR } from './use-static-swr'; @@ -23,8 +23,8 @@ export const useSiteUrl = (initialData?: string): SWRResponse => return useStaticSWR('siteUrl', initialData); }; -export const useCurrentUser = (initialData?: Nullable): SWRResponse, Error> => { - return useStaticSWR, Error>('currentUser', initialData); +export const useCurrentUser = (initialData?: Nullable): SWRResponse, Error> => { + return useStaticSWR, Error>('currentUser', initialData); }; export const useRevisionId = (initialData?: Nullable): SWRResponse, Error> => { diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index 131ac2ad9df..305fbf1ea16 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -317,6 +317,19 @@ ul.pagination { } } } + + .grw-bookmarks-list { + .list-group { + .list-group-item { + background-color: transparent; + } + } + .list-group-flush { + .list-group-item { + border: none; + } + } + } } /* From a469d1c0655e55bf9dbf2b18522d7719c91628e8 Mon Sep 17 00:00:00 2001 From: mudana Date: Tue, 19 Jul 2022 16:38:01 +0800 Subject: [PATCH 003/826] Add tooltip to bookmarks list https://youtrack.weseek.co.jp/issue/GW-7832 - Implement UncontrolledTooltip to bookmark list - Adjust styling on bookmark list hover --- .../app/src/components/Sidebar/Bookmarks.tsx | 36 ++++++++++++------- .../app/src/styles/theme/_apply-colors.scss | 4 +++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 3757618dccb..e8593ac126e 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { DevidedPagePath } from '@growi/core'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; +import { UncontrolledTooltip } from 'reactstrap'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; @@ -20,7 +21,7 @@ const BookmarksItem = ({ data }) => { const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); return ( -
  • +
  • @@ -49,17 +50,28 @@ const Bookmarks = () : JSX.Element => {
    {isEmpty ? t('No bookmarks yet') : ( -
      - - {paginationResult => paginationResult?.docs.map(data => ( - - )) - } - -
    +
    +
      + + {paginationResult => paginationResult?.docs.map(data => ( + <> + + {data.page.path} + + + )) + } + +
    +
    )}
    diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index 305fbf1ea16..d20d0856c74 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -20,6 +20,7 @@ $bgcolor-page-list-group-item-active: lighten($primary, 76%) !default; $color-page-list-group-item-meta: $gray-500 !default; $color-search-page-list-title: $color-global !default; $bgcolor-subnav: darken($bgcolor-global, 3%) !default; +$bgcolor-list-hover: darken($primary, 8%) !default; // override bootstrap variables $body-bg: $bgcolor-global; @@ -322,6 +323,9 @@ ul.pagination { .list-group { .list-group-item { background-color: transparent; + &:hover{ + background-color: $bgcolor-list-hover; + } } } .list-group-flush { From 1f68527cdf695fd9d4016b45d179a961d9ed1965 Mon Sep 17 00:00:00 2001 From: mudana Date: Tue, 19 Jul 2022 18:31:52 +0800 Subject: [PATCH 004/826] Add dropdown button to bookmark list https://youtrack.weseek.co.jp/issue/GW-7837 - Implement PageItemControl to bookmark list item - Exclude duplicate menu item type from PageItemControl - Create styling for bookmark-item --- .../app/src/components/Sidebar/Bookmarks.tsx | 51 ++++++++++++------- packages/app/src/styles/_bookmark-list.scss | 17 +++++++ packages/app/src/styles/style-app.scss | 1 + 3 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 packages/app/src/styles/_bookmark-list.scss diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index e8593ac126e..6108a31a0bf 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -4,12 +4,13 @@ import React from 'react'; import { DevidedPagePath } from '@growi/core'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -import { UncontrolledTooltip } from 'reactstrap'; +import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; import { useCurrentUser } from '~/stores/context'; +import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; import InfiniteScroll from './InfiniteScroll'; @@ -19,15 +20,36 @@ const BookmarksItem = ({ data }) => { const { page } = data; const dPagePath = new DevidedPagePath(page.path, false, true); const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); - + const bookmarkItemId = `bookmark-item-${data._id}`; return ( -
  • -
    -
    - -
    -
    -
  • + <> +
  • +
    +
    + +
    + + + + + + + + {data.page.path} + +
    +
  • + + ); }; @@ -57,16 +79,7 @@ const Bookmarks = () : JSX.Element => { isReachingEnd={isReachingEnd} > {paginationResult => paginationResult?.docs.map(data => ( - <> - - {data.page.path} - - + )) } diff --git a/packages/app/src/styles/_bookmark-list.scss b/packages/app/src/styles/_bookmark-list.scss new file mode 100644 index 00000000000..f23edd79418 --- /dev/null +++ b/packages/app/src/styles/_bookmark-list.scss @@ -0,0 +1,17 @@ +.grw-bookmarks-list{ + .list-group-item { + .grw-visible-on-hover { + display: none; + } + + &:hover { + .grw-visible-on-hover { + display: block; + } + + .grw-count-badge { + display: none; + } + } + } +} diff --git a/packages/app/src/styles/style-app.scss b/packages/app/src/styles/style-app.scss index b74bccde368..5bd7108395b 100644 --- a/packages/app/src/styles/style-app.scss +++ b/packages/app/src/styles/style-app.scss @@ -59,6 +59,7 @@ @import 'page-accessories-modal'; @import 'page-path'; @import 'page-tree'; +@import 'bookmark-list'; @import 'page'; @import 'page-presentation'; @import 'page-history'; From a49f4d02aab2a362c5f55aa63bb1a3c2101662b7 Mon Sep 17 00:00:00 2001 From: mudana Date: Fri, 15 Jul 2022 18:13:20 +0800 Subject: [PATCH 005/826] Show bookmarks list on the sidebar https://youtrack.weseek.co.jp/issue/GW-7832 - Add offset option to user's bookmarks route - Implement SWRInfinite to fetch user's bookmarks list - Modify useCurrentUser return type to IUserHasId - Implement Infinite scroll to bookmarks list on side bar - Add styling for bookmark list --- .../app/src/components/Sidebar/Bookmarks.tsx | 66 +++++++++++++++++-- .../app/src/server/routes/apiv3/bookmarks.js | 2 + packages/app/src/stores/bookmark.ts | 15 +++++ packages/app/src/stores/context.tsx | 6 +- .../app/src/styles/theme/_apply-colors.scss | 13 ++++ 5 files changed, 92 insertions(+), 10 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index b9638a79959..3757618dccb 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,16 +1,68 @@ -import React, { FC } from 'react'; +import React from 'react'; + +import { DevidedPagePath } from '@growi/core'; +import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -const Bookmarks: FC = () => { - const { t } = useTranslation(''); +import LinkedPagePath from '~/models/linked-page-path'; +import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; +import { useCurrentUser } from '~/stores/context'; + +import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; + +import InfiniteScroll from './InfiniteScroll'; + + +const BookmarksItem = ({ data }) => { + const { page } = data; + const dPagePath = new DevidedPagePath(page.path, false, true); + const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); + + return ( +
  • +
    +
    + +
    +
    +
  • + ); + +}; - // TODO Add bookmarks item +BookmarksItem.propTypes = { + data: PropTypes.any, +}; + +const Bookmarks = () : JSX.Element => { + const { t } = useTranslation(''); + const { data: currentUser } = useCurrentUser(); + const swr = useSWRInifiniteBookmarkedPage(currentUser?._id); + const isEmpty = swr.data?.[0].docs.length === 0; + const isReachingEnd = isEmpty || (swr.data && swr.data[0].nextPage == null); return ( -
    -

    {t('Bookmarks')}

    -
    + <> +
    +

    {t('Bookmarks')}

    +
    +
    + {isEmpty ? t('No bookmarks yet') : ( +
      + + {paginationResult => paginationResult?.docs.map(data => ( + + )) + } + +
    + )} +
    + ); }; diff --git a/packages/app/src/server/routes/apiv3/bookmarks.js b/packages/app/src/server/routes/apiv3/bookmarks.js index 39df6d67d8b..a895afceefc 100644 --- a/packages/app/src/server/routes/apiv3/bookmarks.js +++ b/packages/app/src/server/routes/apiv3/bookmarks.js @@ -201,6 +201,7 @@ module.exports = (crowi) => { const { userId } = req.params; const page = req.query.page; const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30; + const offset = page > 0 ? (page - 1) * limit : page; if (userId == null) { return res.apiv3Err('User id is not found or forbidden', 400); @@ -222,6 +223,7 @@ module.exports = (crowi) => { model: 'User', }, }, + offset, page, limit, }, diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 920729237b1..84d328e07b5 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,5 +1,6 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; +import useSWRInfinite, { SWRInfiniteResponse } from 'swr/infinite'; import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -17,3 +18,17 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon }), ); }; + +export const useSWRInifiniteBookmarkedPage = (userId: string | null | undefined) : SWRInfiniteResponse => { + const getKey = (page: number) => { + return userId != null ? `/bookmarks/${userId}/?page=${page + 1}` : null; + }; + return useSWRInfinite( + getKey, + (endpoint: string) => apiv3Get<{ paginationResult }>(endpoint).then(response => response.data?.paginationResult), + { + revalidateFirstPage: false, + revalidateAll: false, + }, + ); +}; diff --git a/packages/app/src/stores/context.tsx b/packages/app/src/stores/context.tsx index 49ff47e0765..337f0aacb3e 100644 --- a/packages/app/src/stores/context.tsx +++ b/packages/app/src/stores/context.tsx @@ -7,7 +7,7 @@ import { SupportedActionType } from '~/interfaces/activity'; import { GrowiRendererConfig } from '~/interfaces/services/renderer'; import { TargetAndAncestors } from '../interfaces/page-listing-results'; -import { IUser } from '../interfaces/user'; +import { IUser, IUserHasId } from '../interfaces/user'; import { useStaticSWR } from './use-static-swr'; @@ -23,8 +23,8 @@ export const useSiteUrl = (initialData?: string): SWRResponse => return useStaticSWR('siteUrl', initialData); }; -export const useCurrentUser = (initialData?: Nullable): SWRResponse, Error> => { - return useStaticSWR, Error>('currentUser', initialData); +export const useCurrentUser = (initialData?: Nullable): SWRResponse, Error> => { + return useStaticSWR, Error>('currentUser', initialData); }; export const useRevisionId = (initialData?: Nullable): SWRResponse, Error> => { diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index 131ac2ad9df..305fbf1ea16 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -317,6 +317,19 @@ ul.pagination { } } } + + .grw-bookmarks-list { + .list-group { + .list-group-item { + background-color: transparent; + } + } + .list-group-flush { + .list-group-item { + border: none; + } + } + } } /* From 8810f113b8b5a5561b17990d32e75d852c7ff658 Mon Sep 17 00:00:00 2001 From: mudana Date: Tue, 19 Jul 2022 16:38:01 +0800 Subject: [PATCH 006/826] Add tooltip to bookmarks list https://youtrack.weseek.co.jp/issue/GW-7832 - Implement UncontrolledTooltip to bookmark list - Adjust styling on bookmark list hover --- .../app/src/components/Sidebar/Bookmarks.tsx | 36 ++++++++++++------- .../app/src/styles/theme/_apply-colors.scss | 4 +++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 3757618dccb..e8593ac126e 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { DevidedPagePath } from '@growi/core'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; +import { UncontrolledTooltip } from 'reactstrap'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; @@ -20,7 +21,7 @@ const BookmarksItem = ({ data }) => { const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); return ( -
  • +
  • @@ -49,17 +50,28 @@ const Bookmarks = () : JSX.Element => {
    {isEmpty ? t('No bookmarks yet') : ( -
      - - {paginationResult => paginationResult?.docs.map(data => ( - - )) - } - -
    +
    +
      + + {paginationResult => paginationResult?.docs.map(data => ( + <> + + {data.page.path} + + + )) + } + +
    +
    )}
    diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index 305fbf1ea16..d20d0856c74 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -20,6 +20,7 @@ $bgcolor-page-list-group-item-active: lighten($primary, 76%) !default; $color-page-list-group-item-meta: $gray-500 !default; $color-search-page-list-title: $color-global !default; $bgcolor-subnav: darken($bgcolor-global, 3%) !default; +$bgcolor-list-hover: darken($primary, 8%) !default; // override bootstrap variables $body-bg: $bgcolor-global; @@ -322,6 +323,9 @@ ul.pagination { .list-group { .list-group-item { background-color: transparent; + &:hover{ + background-color: $bgcolor-list-hover; + } } } .list-group-flush { From 652799206a54220924658ac3863409e680b288a1 Mon Sep 17 00:00:00 2001 From: mudana Date: Tue, 19 Jul 2022 18:31:52 +0800 Subject: [PATCH 007/826] Add dropdown button to bookmark list https://youtrack.weseek.co.jp/issue/GW-7837 - Implement PageItemControl to bookmark list item - Exclude duplicate menu item type from PageItemControl - Create styling for bookmark-item --- .../app/src/components/Sidebar/Bookmarks.tsx | 51 ++++++++++++------- packages/app/src/styles/_bookmark-list.scss | 17 +++++++ packages/app/src/styles/style-app.scss | 1 + 3 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 packages/app/src/styles/_bookmark-list.scss diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index e8593ac126e..6108a31a0bf 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -4,12 +4,13 @@ import React from 'react'; import { DevidedPagePath } from '@growi/core'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -import { UncontrolledTooltip } from 'reactstrap'; +import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; import { useCurrentUser } from '~/stores/context'; +import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; import InfiniteScroll from './InfiniteScroll'; @@ -19,15 +20,36 @@ const BookmarksItem = ({ data }) => { const { page } = data; const dPagePath = new DevidedPagePath(page.path, false, true); const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); - + const bookmarkItemId = `bookmark-item-${data._id}`; return ( -
  • -
    -
    - -
    -
    -
  • + <> +
  • +
    +
    + +
    + + + + + + + + {data.page.path} + +
    +
  • + + ); }; @@ -57,16 +79,7 @@ const Bookmarks = () : JSX.Element => { isReachingEnd={isReachingEnd} > {paginationResult => paginationResult?.docs.map(data => ( - <> - - {data.page.path} - - + )) } diff --git a/packages/app/src/styles/_bookmark-list.scss b/packages/app/src/styles/_bookmark-list.scss new file mode 100644 index 00000000000..f23edd79418 --- /dev/null +++ b/packages/app/src/styles/_bookmark-list.scss @@ -0,0 +1,17 @@ +.grw-bookmarks-list{ + .list-group-item { + .grw-visible-on-hover { + display: none; + } + + &:hover { + .grw-visible-on-hover { + display: block; + } + + .grw-count-badge { + display: none; + } + } + } +} diff --git a/packages/app/src/styles/style-app.scss b/packages/app/src/styles/style-app.scss index b74bccde368..5bd7108395b 100644 --- a/packages/app/src/styles/style-app.scss +++ b/packages/app/src/styles/style-app.scss @@ -59,6 +59,7 @@ @import 'page-accessories-modal'; @import 'page-path'; @import 'page-tree'; +@import 'bookmark-list'; @import 'page'; @import 'page-presentation'; @import 'page-history'; From 1aec73887942cad1aaebd010ce5169a8ac640ae8 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 20 Jul 2022 12:59:24 +0800 Subject: [PATCH 008/826] Implement remove from bookmarks https://youtrack.weseek.co.jp/issue/GW-7839 - Implement remove bookmarks item handler - Add swr mutate to reload bookmark list after delete - Adjust propTypes of bookmark item - Adjust props of BookmarkItem component --- .../app/src/components/Sidebar/Bookmarks.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 6108a31a0bf..aba30ebab35 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,11 +1,12 @@ -import React from 'react'; +import React, { useCallback, useEffect } from 'react'; import { DevidedPagePath } from '@growi/core'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; +import { unbookmark } from '~/client/services/page-operation'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; import { useCurrentUser } from '~/stores/context'; @@ -15,15 +16,20 @@ import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; import InfiniteScroll from './InfiniteScroll'; - -const BookmarksItem = ({ data }) => { +const BookmarksItem = ({ data, swr }) : JSX.Element => { const { page } = data; const dPagePath = new DevidedPagePath(page.path, false, true); const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); const bookmarkItemId = `bookmark-item-${data._id}`; + + const bookmarkMenuItemClickHandler = useCallback(async() => { + await unbookmark(page.id); + swr.mutate(); + }, [swr, page]); + return ( <> -
  • +
  • @@ -32,6 +38,7 @@ const BookmarksItem = ({ data }) => { pageId={page._id} isEnableActions forceHideMenuItems={[MenuItemType.DUPLICATE]} + onClickBookmarkMenuItem={bookmarkMenuItemClickHandler} > @@ -44,7 +51,7 @@ const BookmarksItem = ({ data }) => { placement="left" target={bookmarkItemId} > - {data.page.path} + {page.path}
  • @@ -56,6 +63,7 @@ const BookmarksItem = ({ data }) => { BookmarksItem.propTypes = { data: PropTypes.any, + swr: PropTypes.any, }; const Bookmarks = () : JSX.Element => { @@ -65,6 +73,10 @@ const Bookmarks = () : JSX.Element => { const isEmpty = swr.data?.[0].docs.length === 0; const isReachingEnd = isEmpty || (swr.data && swr.data[0].nextPage == null); + useEffect(() => { + swr.mutate(); + }, []); + return ( <>
    @@ -79,7 +91,7 @@ const Bookmarks = () : JSX.Element => { isReachingEnd={isReachingEnd} > {paginationResult => paginationResult?.docs.map(data => ( - + )) } From de7a8c908d20ca20a852d0531aae367e3a09296e Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 20 Jul 2022 16:47:52 +0800 Subject: [PATCH 009/826] Implement rename page https://youtrack.weseek.co.jp/issue/GW-7840 - Implement click rename handler - Implement ClosableTextInput for new name input - Add method to rename page and update bookmark list --- .../app/src/components/Sidebar/Bookmarks.tsx | 69 +++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index aba30ebab35..6116e0a0b07 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,44 +1,103 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; -import { DevidedPagePath } from '@growi/core'; +import nodePath from 'path'; + +import { DevidedPagePath, pathUtils } from '@growi/core'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import { unbookmark } from '~/client/services/page-operation'; +import { toastError, toastSuccess } from '~/client/util/apiNotification'; +import { apiv3Put } from '~/client/util/apiv3-client'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; import { useCurrentUser } from '~/stores/context'; +import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; import InfiniteScroll from './InfiniteScroll'; const BookmarksItem = ({ data, swr }) : JSX.Element => { + const { t } = useTranslation(''); const { page } = data; const dPagePath = new DevidedPagePath(page.path, false, true); const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); const bookmarkItemId = `bookmark-item-${data._id}`; + const [isRenameInputShown, setRenameInputShown] = useState(false); const bookmarkMenuItemClickHandler = useCallback(async() => { await unbookmark(page.id); swr.mutate(); }, [swr, page]); + const renameMenuItemClickHandler = useCallback(() => { + setRenameInputShown(true); + }, []); + + const inputValidator = (title: string | null): AlertInfo | null => { + if (title == null || title === '' || title.trim() === '') { + return { + type: AlertType.WARNING, + message: t('form_validation.title_required'), + }; + } + + return null; + }; + + const onPressEnterForRenameHandler = useCallback(async(inputText: string) => { + const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); + const newPagePath = nodePath.resolve(parentPath, inputText); + + if (newPagePath === page.path) { + setRenameInputShown(false); + return; + } + + try { + setRenameInputShown(false); + await apiv3Put('/pages/rename', { + pageId: page._id, + revisionId: page.revision, + newPagePath, + }); + swr.mutate(); + toastSuccess(t('renamed_pages', { path: page.path })); + } + catch (err) { + setRenameInputShown(true); + toastError(err); + } + }, [swr, page, t]); + return ( <>
  • -
    - -
    + { isRenameInputShown ? ( + { setRenameInputShown(false) }} + onPressEnter={onPressEnterForRenameHandler} + inputValidator={inputValidator} + /> + ) : ( +
    + +
    + )} + From d27fc27c077bdd87bde7b639b54939b0787db514 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 20 Jul 2022 18:42:48 +0800 Subject: [PATCH 010/826] Implement delete page from bookmark sidebar https://youtrack.weseek.co.jp/issue/GW-7841 - Add onClickDeleteMenuItem props to PageItemControl - Create method to delete page and reload the bookmark list --- .../app/src/components/Sidebar/Bookmarks.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 6116e0a0b07..73a40c8b798 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -11,9 +11,12 @@ import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import { unbookmark } from '~/client/services/page-operation'; import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Put } from '~/client/util/apiv3-client'; +import { IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; +import { OnDeletedFunction } from '~/interfaces/ui'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; import { useCurrentUser } from '~/stores/context'; +import { usePageDeleteModal } from '~/stores/modal'; import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; @@ -28,6 +31,7 @@ const BookmarksItem = ({ data, swr }) : JSX.Element => { const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); const bookmarkItemId = `bookmark-item-${data._id}`; const [isRenameInputShown, setRenameInputShown] = useState(false); + const { open: openDeleteModal } = usePageDeleteModal(); const bookmarkMenuItemClickHandler = useCallback(async() => { await unbookmark(page.id); @@ -74,6 +78,42 @@ const BookmarksItem = ({ data, swr }) : JSX.Element => { } }, [swr, page, t]); + + const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { + const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => { + const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + if (typeof pathOrPathsToDelete !== 'string') { + return; + } + const path = pathOrPathsToDelete; + + if (isCompletely) { + toastSuccess(t('deleted_pages_completely', { path })); + } + else { + toastSuccess(t('deleted_pages', { path })); + } + swr.mutate(); + }; + openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); + }; + + if (page._id == null || page.path == null) { + throw Error('_id and path must not be null.'); + } + + const pageToDelete: IPageToDeleteWithMeta = { + data: { + _id: page._id, + revision: page.revision as string, + path: page.path, + }, + meta: pageInfo, + }; + + onClickDeleteMenuItem(pageToDelete); + }, [page, swr, openDeleteModal, t]); + return ( <>
  • @@ -98,6 +138,7 @@ const BookmarksItem = ({ data, swr }) : JSX.Element => { forceHideMenuItems={[MenuItemType.DUPLICATE]} onClickBookmarkMenuItem={bookmarkMenuItemClickHandler} onClickRenameMenuItem={renameMenuItemClickHandler} + onClickDeleteMenuItem={deleteMenuItemClickHandler} > From a087164173f2efe8c8cae54f7fef020c60a082b5 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 21 Jul 2022 17:19:47 +0800 Subject: [PATCH 011/826] Update type of props https://youtrack.weseek.co.jp/issue/GW-7841 - Remove BookmarksItem.propTypes and add new type of Props - Update BookmarksItem props and constant - Adjust page id parameter of unbookmark method - Adjust bookmarkItemId value - Update implementation of BookmarksItem --- .../app/src/components/Sidebar/Bookmarks.tsx | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 73a40c8b798..787daecdceb 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,17 +1,19 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { + FC, useCallback, useEffect, useState, +} from 'react'; import nodePath from 'path'; import { DevidedPagePath, pathUtils } from '@growi/core'; -import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; +import type { SWRInfiniteResponse } from 'swr/infinite'; import { unbookmark } from '~/client/services/page-operation'; import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Put } from '~/client/util/apiv3-client'; -import { IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; +import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; import { OnDeletedFunction } from '~/interfaces/ui'; import LinkedPagePath from '~/models/linked-page-path'; import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; @@ -24,17 +26,21 @@ import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; import InfiniteScroll from './InfiniteScroll'; -const BookmarksItem = ({ data, swr }) : JSX.Element => { - const { t } = useTranslation(''); - const { page } = data; +type Props = { + page: IPageHasId, + swr: SWRInfiniteResponse +} +const BookmarksItem:FC = (props: Props) : JSX.Element => { + const { t } = useTranslation(); + const { page, swr } = props; const dPagePath = new DevidedPagePath(page.path, false, true); const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); - const bookmarkItemId = `bookmark-item-${data._id}`; + const bookmarkItemId = `bookmark-item-${page._id}`; const [isRenameInputShown, setRenameInputShown] = useState(false); const { open: openDeleteModal } = usePageDeleteModal(); const bookmarkMenuItemClickHandler = useCallback(async() => { - await unbookmark(page.id); + await unbookmark(page._id); swr.mutate(); }, [swr, page]); @@ -161,13 +167,8 @@ const BookmarksItem = ({ data, swr }) : JSX.Element => { }; -BookmarksItem.propTypes = { - data: PropTypes.any, - swr: PropTypes.any, -}; - const Bookmarks = () : JSX.Element => { - const { t } = useTranslation(''); + const { t } = useTranslation(); const { data: currentUser } = useCurrentUser(); const swr = useSWRInifiniteBookmarkedPage(currentUser?._id); const isEmpty = swr.data?.[0].docs.length === 0; @@ -191,7 +192,7 @@ const Bookmarks = () : JSX.Element => { isReachingEnd={isReachingEnd} > {paginationResult => paginationResult?.docs.map(data => ( - + )) } From 09ab2d8ab3e199aa5cc61d4de2255a547f3c78d5 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 21 Jul 2022 17:43:58 +0800 Subject: [PATCH 012/826] Update tooltip placement https://youtrack.weseek.co.jp/issue/GW-7832 - Fix tooltip misplacement --- packages/app/src/components/Sidebar/Bookmarks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index e8593ac126e..2f56a9aefb1 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -61,7 +61,7 @@ const Bookmarks = () : JSX.Element => { {data.page.path} From 215a5c62aededcf5d4ac9b25b7a7f69b32641204 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 22 Jul 2022 10:18:12 +0800 Subject: [PATCH 013/826] Fix indentation --- packages/app/src/components/Sidebar/Bookmarks.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 4fe80290bc6..2a6089bd739 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -81,14 +81,14 @@ const Bookmarks = () : JSX.Element => { {paginationResult => paginationResult?.docs.map(data => ( <> - + {data.page.path} - + )) } From 52d77ac287c0ab8f4c02bb9ffa6a49599045b712 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 22 Jul 2022 20:49:46 +0800 Subject: [PATCH 014/826] Fix BookmarksItem component https://youtrack.weseek.co.jp/issue/GW-7832 - Add type definition for BookmarkItem props - Update useTranslation - Update generation of bookmarked page list - Move UncontrolledTooltip component to BookmarkItem - Update page link and title - Update tooltip content with former link - Remove implementation of InfiniteScroll and SWRInfinite - Add rendering condition for guestUser - Update get user bookmarks route - Create styling for bookmarks - Add bookmarks styling for dark/light mode color --- .../app/src/components/Sidebar/Bookmarks.tsx | 144 +++++++++++------- .../app/src/server/routes/apiv3/bookmarks.js | 2 - packages/app/src/stores/bookmark.ts | 15 -- packages/app/src/styles/_bookmarks.scss | 38 +++++ packages/app/src/styles/style-app.scss | 1 + .../src/styles/theme/_apply-colors-dark.scss | 13 ++ .../src/styles/theme/_apply-colors-light.scss | 13 ++ .../app/src/styles/theme/_apply-colors.scss | 4 - 8 files changed, 157 insertions(+), 73 deletions(-) create mode 100644 packages/app/src/styles/_bookmarks.scss diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 2f56a9aefb1..9ba007418be 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,79 +1,119 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { DevidedPagePath } from '@growi/core'; -import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip } from 'reactstrap'; -import LinkedPagePath from '~/models/linked-page-path'; -import { useSWRInifiniteBookmarkedPage } from '~/stores/bookmark'; -import { useCurrentUser } from '~/stores/context'; +import { toastError } from '~/client/util/apiNotification'; +import { apiv3Get } from '~/client/util/apiv3-client'; +import { IPageHasId } from '~/interfaces/page'; +import { useCurrentUser, useIsGuestUser } from '~/stores/context'; +import loggerFactory from '~/utils/logger'; -import PagePathHierarchicalLink from '../PagePathHierarchicalLink'; +const logger = loggerFactory('growi:BookmarkList'); -import InfiniteScroll from './InfiniteScroll'; +// TODO: Adjust pagination +const ACTIVE_PAGE = 1; +type Props = { + pages: IPageHasId[] +} -const BookmarksItem = ({ data }) => { - const { page } = data; - const dPagePath = new DevidedPagePath(page.path, false, true); - const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter); +const BookmarksItem = (props: Props) => { + const { pages } = props; - return ( -
  • -
    -
    - -
    + const generateBookmarkedPageList = pages.map((page) => { + const dPagePath = new DevidedPagePath(page.path, false, true); + const { latter: pageTitle, former: formerPagePath } = dPagePath; + return ( +
    +
  • + +

    {pageTitle}

    +
    +
  • + + { formerPagePath || '/' } +
    - - ); + ); + }); -}; -BookmarksItem.propTypes = { - data: PropTypes.any, + return ( + <> +
      +
      + {generateBookmarkedPageList} +
      +
    + + ); }; + const Bookmarks = () : JSX.Element => { - const { t } = useTranslation(''); + const { t } = useTranslation(); const { data: currentUser } = useCurrentUser(); - const swr = useSWRInifiniteBookmarkedPage(currentUser?._id); - const isEmpty = swr.data?.[0].docs.length === 0; - const isReachingEnd = isEmpty || (swr.data && swr.data[0].nextPage == null); + const { data: isGuestUser } = useIsGuestUser(); + const [pages, setPages] = useState([]); + + const getMyBookmarkList = useCallback(async() => { + // TODO: Adjust pagination + const page = ACTIVE_PAGE; + + try { + const res = await apiv3Get(`/bookmarks/${currentUser?._id}`, { page }); + const { paginationResult } = res.data; + setPages(paginationResult.docs.map((page) => { + return { + ...page.page, + }; + })); + } + catch (error) { + logger.error('failed to fetch data', error); + toastError(error, 'Error occurred in bookmark page list'); + } + }, [currentUser]); + + useEffect(() => { + getMyBookmarkList(); + }, [getMyBookmarkList]); return ( <>

    {t('Bookmarks')}

    -
    - {isEmpty ? t('No bookmarks yet') : ( -
    -
      - - {paginationResult => paginationResult?.docs.map(data => ( - <> - - {data.page.path} - - - )) - } - -
    -
    - )} -
    + + { isGuestUser + ? ( +

    + { t('Not available for guest') } +

    + ) + : ( + <> + { pages.length === 0 + ? ( +

    + { t('No bookmarks yet') } +

    + ) + : ( + + ) + } + + ) + } ); diff --git a/packages/app/src/server/routes/apiv3/bookmarks.js b/packages/app/src/server/routes/apiv3/bookmarks.js index a895afceefc..39df6d67d8b 100644 --- a/packages/app/src/server/routes/apiv3/bookmarks.js +++ b/packages/app/src/server/routes/apiv3/bookmarks.js @@ -201,7 +201,6 @@ module.exports = (crowi) => { const { userId } = req.params; const page = req.query.page; const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30; - const offset = page > 0 ? (page - 1) * limit : page; if (userId == null) { return res.apiv3Err('User id is not found or forbidden', 400); @@ -223,7 +222,6 @@ module.exports = (crowi) => { model: 'User', }, }, - offset, page, limit, }, diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 84d328e07b5..920729237b1 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,6 +1,5 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; -import useSWRInfinite, { SWRInfiniteResponse } from 'swr/infinite'; import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -18,17 +17,3 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon }), ); }; - -export const useSWRInifiniteBookmarkedPage = (userId: string | null | undefined) : SWRInfiniteResponse => { - const getKey = (page: number) => { - return userId != null ? `/bookmarks/${userId}/?page=${page + 1}` : null; - }; - return useSWRInfinite( - getKey, - (endpoint: string) => apiv3Get<{ paginationResult }>(endpoint).then(response => response.data?.paginationResult), - { - revalidateFirstPage: false, - revalidateAll: false, - }, - ); -}; diff --git a/packages/app/src/styles/_bookmarks.scss b/packages/app/src/styles/_bookmarks.scss new file mode 100644 index 00000000000..b66dec81cf8 --- /dev/null +++ b/packages/app/src/styles/_bookmarks.scss @@ -0,0 +1,38 @@ +$grw-sidebar-content-header-height: 58px; +$grw-sidebar-content-footer-height: 50px; + +.grw-bookmarks-list { + min-height: calc(100vh - ($grw-navbar-height + $grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); + + .btn-page-item-control { + .icon-plus::before { + font-size: 18px; + } + } + + .list-group-item { + .grw-visible-on-hover { + display: none; + } + + &:hover { + .grw-visible-on-hover { + display: block; + } + } + + .grw-bookmarks-title-anchor { + width: 100%; + overflow: hidden; + text-decoration: none; + } + + } + .grw-bookmarks-item-container { + .list-group-item { + min-width: 35px; + height: 40px; + } + } + +} diff --git a/packages/app/src/styles/style-app.scss b/packages/app/src/styles/style-app.scss index b74bccde368..7a89b2e348b 100644 --- a/packages/app/src/styles/style-app.scss +++ b/packages/app/src/styles/style-app.scss @@ -36,6 +36,7 @@ // growi component @import 'admin'; @import 'attachments'; +@import 'bookmarks'; @import 'comment'; @import 'comment_growi'; @import 'drawio'; diff --git a/packages/app/src/styles/theme/_apply-colors-dark.scss b/packages/app/src/styles/theme/_apply-colors-dark.scss index 29dcf72cd8e..66506ac0b22 100644 --- a/packages/app/src/styles/theme/_apply-colors-dark.scss +++ b/packages/app/src/styles/theme/_apply-colors-dark.scss @@ -300,6 +300,19 @@ ul.pagination { box-shadow: none !important; } } + + // bookmarks + .grw-bookmarks-list { + @include override-list-group-item-for-pagetree( + $color-sidebar-context, + lighten($bgcolor-sidebar-context, 8%), + lighten($bgcolor-sidebar-context, 15%), + darken($color-sidebar-context, 15%), + darken($color-sidebar-context, 10%), + lighten($bgcolor-sidebar-context, 18%), + lighten($bgcolor-sidebar-context, 24%) + ); + } .private-legacy-pages-link { &:hover { background: $bgcolor-list-hover; diff --git a/packages/app/src/styles/theme/_apply-colors-light.scss b/packages/app/src/styles/theme/_apply-colors-light.scss index 5b6a663f9ed..41b4ed9bdcb 100644 --- a/packages/app/src/styles/theme/_apply-colors-light.scss +++ b/packages/app/src/styles/theme/_apply-colors-light.scss @@ -198,6 +198,19 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active; @include button-outline-svg-icon-variant($gray-400, $primary); } } + + // bookmark + .grw-bookmarks-list { + @include override-list-group-item-for-pagetree( + $color-sidebar-context, + darken($bgcolor-sidebar-context, 5%), + darken($bgcolor-sidebar-context, 12%), + lighten($color-sidebar-context, 10%), + lighten($color-sidebar-context, 8%), + darken($bgcolor-sidebar-context, 15%), + darken($bgcolor-sidebar-context, 24%) + ); + } .private-legacy-pages-link { &:hover { background: $bgcolor-list-hover; diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index d20d0856c74..305fbf1ea16 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -20,7 +20,6 @@ $bgcolor-page-list-group-item-active: lighten($primary, 76%) !default; $color-page-list-group-item-meta: $gray-500 !default; $color-search-page-list-title: $color-global !default; $bgcolor-subnav: darken($bgcolor-global, 3%) !default; -$bgcolor-list-hover: darken($primary, 8%) !default; // override bootstrap variables $body-bg: $bgcolor-global; @@ -323,9 +322,6 @@ ul.pagination { .list-group { .list-group-item { background-color: transparent; - &:hover{ - background-color: $bgcolor-list-hover; - } } } .list-group-flush { From 1c1b1a9aa6ae63622fad180efd6704a4baeccb7d Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 25 Jul 2022 14:29:38 +0800 Subject: [PATCH 015/826] Fix conditional render https://youtrack.weseek.co.jp/issue/GW-7832 - Replace ternary operator of conditional rendering with renderBar method - Adjust Todo comment - Remove unnecessary styling --- .../app/src/components/Sidebar/Bookmarks.tsx | 46 +++++++++---------- .../app/src/styles/theme/_apply-colors.scss | 12 ----- 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 9ba007418be..933a99bce2e 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -13,7 +13,7 @@ import loggerFactory from '~/utils/logger'; const logger = loggerFactory('growi:BookmarkList'); -// TODO: Adjust pagination +// TODO: Remove pagination and apply scrolling (not infinity) const ACTIVE_PAGE = 1; type Props = { @@ -65,7 +65,7 @@ const Bookmarks = () : JSX.Element => { const [pages, setPages] = useState([]); const getMyBookmarkList = useCallback(async() => { - // TODO: Adjust pagination + // TODO: Remove pagination and apply scrolling (not infinity) const page = ACTIVE_PAGE; try { @@ -87,33 +87,31 @@ const Bookmarks = () : JSX.Element => { getMyBookmarkList(); }, [getMyBookmarkList]); + const renderBar = () => { + if (isGuestUser) { + return ( +

    + { t('Not available for guest') } +

    + ); + } + if (pages.length === 0) { + return ( +

    + { t('No bookmarks yet') } +

    + ); + } + return null; + }; + return ( <>

    {t('Bookmarks')}

    - - { isGuestUser - ? ( -

    - { t('Not available for guest') } -

    - ) - : ( - <> - { pages.length === 0 - ? ( -

    - { t('No bookmarks yet') } -

    - ) - : ( - - ) - } - - ) - } + { renderBar() } + ); diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index 305fbf1ea16..c8a51c158dc 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -318,18 +318,6 @@ ul.pagination { } } - .grw-bookmarks-list { - .list-group { - .list-group-item { - background-color: transparent; - } - } - .list-group-flush { - .list-group-item { - border: none; - } - } - } } /* From c238090a84f6b8ddb9de58f0cd5e22e01f2619df Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 25 Jul 2022 15:36:00 +0800 Subject: [PATCH 016/826] Move tooltip and PageItemControl https://youtrack.weseek.co.jp/issue/GW-7837 - Move Tooltip position - Fix PageItemControl position and styling --- .../app/src/components/Sidebar/Bookmarks.tsx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 03db3208018..ee0c1ee5438 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -30,15 +30,14 @@ const BookmarksItem = (props: Props) => { const dPagePath = new DevidedPagePath(page.path, false, true); const { latter: pageTitle, former: formerPagePath } = dPagePath; const bookmarkItemId = `bookmark-item-${page._id}`; + return ( -
    +
  • {pageTitle}

    -
  • - - { - - - { formerPagePath || '/' } - + + { formerPagePath || '/' } + +
    ); }); From 826bbcaa4554f0c5295775ad268397a2eac3b9be Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 25 Jul 2022 19:20:47 +0800 Subject: [PATCH 017/826] Code improvement https://youtrack.weseek.co.jp/issue/GW-7839 - Use SWR to fetch currentUser bookmark list - Implement useSWRCurrentUserBookmark to load bookmark and refresh bookmark list - Add refreshBookmarkList props to BookmarksItem component --- .../app/src/components/Sidebar/Bookmarks.tsx | 52 ++++++------------- packages/app/src/stores/bookmark.ts | 18 ++++++- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index ee0c1ee5438..53639a442f3 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,36 +1,40 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React from 'react'; import { DevidedPagePath } from '@growi/core'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; -import { toastError } from '~/client/util/apiNotification'; -import { apiv3Get } from '~/client/util/apiv3-client'; +import { unbookmark } from '~/client/services/page-operation'; import { IPageHasId } from '~/interfaces/page'; +import { useSWRCurrentUserBookmark } from '~/stores/bookmark'; import { useCurrentUser, useIsGuestUser } from '~/stores/context'; -import loggerFactory from '~/utils/logger'; - import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; -const logger = loggerFactory('growi:BookmarkList'); // TODO: Remove pagination and apply scrolling (not infinity) const ACTIVE_PAGE = 1; type Props = { - pages: IPageHasId[] + pages: IPageHasId[] | undefined, + refreshBookmarkList: () => void } const BookmarksItem = (props: Props) => { - const { pages } = props; + const { pages, refreshBookmarkList } = props; - const generateBookmarkedPageList = pages.map((page) => { + const generateBookmarkedPageList = pages?.map((page) => { const dPagePath = new DevidedPagePath(page.path, false, true); const { latter: pageTitle, former: formerPagePath } = dPagePath; const bookmarkItemId = `bookmark-item-${page._id}`; + const bookmarkMenuItemClickHandler = (async() => { + await unbookmark(page._id); + refreshBookmarkList(); + }); + + return (
  • @@ -41,6 +45,7 @@ const BookmarksItem = (props: Props) => { pageId={page._id} isEnableActions forceHideMenuItems={[MenuItemType.DUPLICATE]} + onClickBookmarkMenuItem={bookmarkMenuItemClickHandler} > @@ -76,30 +81,7 @@ const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: currentUser } = useCurrentUser(); const { data: isGuestUser } = useIsGuestUser(); - const [pages, setPages] = useState([]); - - const getMyBookmarkList = useCallback(async() => { - // TODO: Remove pagination and apply scrolling (not infinity) - const page = ACTIVE_PAGE; - - try { - const res = await apiv3Get(`/bookmarks/${currentUser?._id}`, { page }); - const { paginationResult } = res.data; - setPages(paginationResult.docs.map((page) => { - return { - ...page.page, - }; - })); - } - catch (error) { - logger.error('failed to fetch data', error); - toastError(error, 'Error occurred in bookmark page list'); - } - }, [currentUser]); - - useEffect(() => { - getMyBookmarkList(); - }, [getMyBookmarkList]); + const { data: pages, mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmark(currentUser?._id, ACTIVE_PAGE); const renderBar = () => { if (isGuestUser) { @@ -109,7 +91,7 @@ const Bookmarks = () : JSX.Element => { ); } - if (pages.length === 0) { + if (pages?.length === 0) { return (

    { t('No bookmarks yet') } @@ -125,7 +107,7 @@ const Bookmarks = () : JSX.Element => {

    {t('Bookmarks')}

  • { renderBar() } - + ); diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 920729237b1..bd00710590c 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,6 +1,8 @@ -import { SWRResponse } from 'swr'; +import useSWR, { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; +import { IPageHasId } from '~/interfaces/page'; + import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -17,3 +19,17 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon }), ); }; + +export const useSWRCurrentUserBookmark = (userId: string | null | undefined, page: number | null | undefined): SWRResponse => { + return useSWR( + userId != null ? `/bookmarks/${userId}` : null, + endpoint => apiv3Get(endpoint, { page }).then((response) => { + const { paginationResult } = response.data; + return paginationResult.docs.map((item) => { + return { + ...item.page, + }; + }); + }), + ); +}; From 622bf5a1853b1a45b6edd1bf885e9b600fa05bbd Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 25 Jul 2022 19:37:54 +0800 Subject: [PATCH 018/826] fix conditional rendering --- .../app/src/components/Sidebar/Bookmarks.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 933a99bce2e..cf1253f4205 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -87,14 +87,7 @@ const Bookmarks = () : JSX.Element => { getMyBookmarkList(); }, [getMyBookmarkList]); - const renderBar = () => { - if (isGuestUser) { - return ( -

    - { t('Not available for guest') } -

    - ); - } + const renderBookmarkList = () => { if (pages.length === 0) { return (

    @@ -102,7 +95,7 @@ const Bookmarks = () : JSX.Element => {

    ); } - return null; + return ; }; return ( @@ -110,8 +103,15 @@ const Bookmarks = () : JSX.Element => {

    {t('Bookmarks')}

    - { renderBar() } - + { isGuestUser + ? ( +

    + { t('Not available for guest') } +

    + ) : ( + { renderBookmarkList } + ) + } ); From ab63c421dd831549df16cfe1cb53c3aaccb186b8 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 25 Jul 2022 19:44:07 +0800 Subject: [PATCH 019/826] Merge branch 'feat/gw7832-show-bookmarks-item-on-sidebar' into feat/gw7837-add-dropdown-button-to-bookmark-item --- .../app/src/components/Sidebar/Bookmarks.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index ee0c1ee5438..2729170fdcc 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -101,14 +101,7 @@ const Bookmarks = () : JSX.Element => { getMyBookmarkList(); }, [getMyBookmarkList]); - const renderBar = () => { - if (isGuestUser) { - return ( -

    - { t('Not available for guest') } -

    - ); - } + const renderBookmarkList = () => { if (pages.length === 0) { return (

    @@ -116,7 +109,7 @@ const Bookmarks = () : JSX.Element => {

    ); } - return null; + return ; }; return ( @@ -124,8 +117,15 @@ const Bookmarks = () : JSX.Element => {

    {t('Bookmarks')}

    - { renderBar() } - + { isGuestUser + ? ( +

    + { t('Not available for guest') } +

    + ) : ( + { renderBookmarkList } + ) + } ); From f8661bda755818c2d6b9622e2f8517f370857004 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 25 Jul 2022 20:33:40 +0800 Subject: [PATCH 020/826] Fix conditional rendering --- packages/app/src/components/Sidebar/Bookmarks.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index cf1253f4205..c687c8efe28 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -87,7 +87,7 @@ const Bookmarks = () : JSX.Element => { getMyBookmarkList(); }, [getMyBookmarkList]); - const renderBookmarkList = () => { + const renderBookmarksItem = () => { if (pages.length === 0) { return (

    @@ -108,10 +108,9 @@ const Bookmarks = () : JSX.Element => {

    { t('Not available for guest') }

    - ) : ( - { renderBookmarkList } - ) + ) : renderBookmarksItem() } + ); From 2087447b7a6ebf46cfb4b0765f6e3cb47a9e1d5b Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 25 Jul 2022 20:46:11 +0800 Subject: [PATCH 021/826] Update Bookmarks.tsx --- packages/app/src/components/Sidebar/Bookmarks.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index cd08411a19b..6f0edd600b8 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -85,14 +85,14 @@ const Bookmarks = () : JSX.Element => { const renderBookmarksItem = () => { - if (pages.length === 0) { + if (pages?.length === 0) { return (

    { t('No bookmarks yet') }

    ); } - return ; + return ; }; return ( From 2d0646cede4d0f4d0a846e0af8c44de2886b6cac Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 26 Jul 2022 15:34:49 +0800 Subject: [PATCH 022/826] Refresh bookmarks list on delete https://youtrack.weseek.co.jp/issue/GW-7839 - Reload bookmarks list after delete --- .../app/src/components/Sidebar/Bookmarks.tsx | 32 ++++++++++++++++--- packages/app/src/stores/bookmark.ts | 18 +---------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 6f0edd600b8..28ce109edc3 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,18 +1,20 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { DevidedPagePath } from '@growi/core'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import { unbookmark } from '~/client/services/page-operation'; +import { toastError } from '~/client/util/apiNotification'; +import { apiv3Get } from '~/client/util/apiv3-client'; import { IPageHasId } from '~/interfaces/page'; -import { useSWRCurrentUserBookmark } from '~/stores/bookmark'; import { useCurrentUser, useIsGuestUser } from '~/stores/context'; +import loggerFactory from '~/utils/logger'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; - +const logger = loggerFactory('growi:BookmarkList'); // TODO: Remove pagination and apply scrolling (not infinity) const ACTIVE_PAGE = 1; @@ -81,8 +83,28 @@ const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: currentUser } = useCurrentUser(); const { data: isGuestUser } = useIsGuestUser(); - const { data: pages, mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmark(currentUser?._id, ACTIVE_PAGE); + const [pages, setPages] = useState([]); + const page = ACTIVE_PAGE; + + const getMyBookmarkList = useCallback(async() => { + try { + const res = await apiv3Get(`/bookmarks/${currentUser?._id}`, { page }); + const { paginationResult } = res.data; + setPages(paginationResult.docs.map((page) => { + return { + ...page.page, + }; + })); + } + catch (error) { + logger.error('failed to fetch data', error); + toastError(error, 'Error occurred in bookmark page list'); + } + }, [currentUser, page]); + useEffect(() => { + getMyBookmarkList(); + }, [getMyBookmarkList]); const renderBookmarksItem = () => { if (pages?.length === 0) { @@ -92,7 +114,7 @@ const Bookmarks = () : JSX.Element => { ); } - return ; + return ; }; return ( diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index bd00710590c..920729237b1 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,8 +1,6 @@ -import useSWR, { SWRResponse } from 'swr'; +import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; -import { IPageHasId } from '~/interfaces/page'; - import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -19,17 +17,3 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon }), ); }; - -export const useSWRCurrentUserBookmark = (userId: string | null | undefined, page: number | null | undefined): SWRResponse => { - return useSWR( - userId != null ? `/bookmarks/${userId}` : null, - endpoint => apiv3Get(endpoint, { page }).then((response) => { - const { paginationResult } = response.data; - return paginationResult.docs.map((item) => { - return { - ...item.page, - }; - }); - }), - ); -}; From 7b0408fb29f0bb3cd0b8b4ea50c46833972e7d17 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 26 Jul 2022 15:43:03 +0800 Subject: [PATCH 023/826] Remove style https://youtrack.weseek.co.jp/issue/GW-7837 - Remove unused scss file --- packages/app/src/styles/_bookmark-list.scss | 17 ----------------- packages/app/src/styles/style-app.scss | 1 - 2 files changed, 18 deletions(-) delete mode 100644 packages/app/src/styles/_bookmark-list.scss diff --git a/packages/app/src/styles/_bookmark-list.scss b/packages/app/src/styles/_bookmark-list.scss deleted file mode 100644 index f23edd79418..00000000000 --- a/packages/app/src/styles/_bookmark-list.scss +++ /dev/null @@ -1,17 +0,0 @@ -.grw-bookmarks-list{ - .list-group-item { - .grw-visible-on-hover { - display: none; - } - - &:hover { - .grw-visible-on-hover { - display: block; - } - - .grw-count-badge { - display: none; - } - } - } -} diff --git a/packages/app/src/styles/style-app.scss b/packages/app/src/styles/style-app.scss index dc70a11d3ee..7a89b2e348b 100644 --- a/packages/app/src/styles/style-app.scss +++ b/packages/app/src/styles/style-app.scss @@ -60,7 +60,6 @@ @import 'page-accessories-modal'; @import 'page-path'; @import 'page-tree'; -@import 'bookmark-list'; @import 'page'; @import 'page-presentation'; @import 'page-history'; From c0d0655c22f6a9e4ace35da7f3c4c882ee926dcd Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 26 Jul 2022 18:12:41 +0800 Subject: [PATCH 024/826] Code improvement https://youtrack.weseek.co.jp/issue/GW-7840 - Remove unnecessary styling - Rename onPressEnterForRenameHandler to pressEnterForRenameHandler - Remove SWRInfinite route for bookmarks - Change BookmakrsItem props type - Move generateBookmarkedPageList to Bookmarks component --- .../app/src/components/Sidebar/Bookmarks.tsx | 132 +++++++++--------- .../src/components/Sidebar/PageTree/Item.tsx | 4 +- packages/app/src/stores/bookmark.ts | 15 -- packages/app/src/styles/_bookmark-list.scss | 17 --- packages/app/src/styles/style-app.scss | 1 - 5 files changed, 70 insertions(+), 99 deletions(-) delete mode 100644 packages/app/src/styles/_bookmark-list.scss diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index ef3c540629c..0edaa24cce8 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import nodePath from 'path'; + import { DevidedPagePath, pathUtils } from '@growi/core'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; @@ -12,6 +13,7 @@ import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client'; import { IPageHasId } from '~/interfaces/page'; import { useCurrentUser, useIsGuestUser } from '~/stores/context'; import loggerFactory from '~/utils/logger'; + import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; @@ -20,67 +22,65 @@ const logger = loggerFactory('growi:BookmarkList'); const ACTIVE_PAGE = 1; type Props = { - pages: IPageHasId[] | undefined, + page: IPageHasId, refreshBookmarkList: () => void } const BookmarksItem = (props: Props) => { const { t } = useTranslation(); - const { pages, refreshBookmarkList } = props; - - const generateBookmarkedPageList = pages?.map((page) => { - const dPagePath = new DevidedPagePath(page.path, false, true); - const { latter: pageTitle, former: formerPagePath } = dPagePath; - const bookmarkItemId = `bookmark-item-${page._id}`; - const [isRenameInputShown, setRenameInputShown] = useState(false); + const { page, refreshBookmarkList } = props; + const [isRenameInputShown, setRenameInputShown] = useState(false); + const dPagePath = new DevidedPagePath(page.path, false, true); + const { latter: pageTitle, former: formerPagePath } = dPagePath; + const bookmarkItemId = `bookmark-item-${page._id}`; + + + const bookmarkMenuItemClickHandler = useCallback(async() => { + await unbookmark(page._id); + refreshBookmarkList(); + }, [page, refreshBookmarkList]); + + const renameMenuItemClickHandler = useCallback(() => { + setRenameInputShown(true); + }, []); + + const inputValidator = (title: string | null): AlertInfo | null => { + if (title == null || title === '' || title.trim() === '') { + return { + type: AlertType.WARNING, + message: t('form_validation.title_required'), + }; + } - const bookmarkMenuItemClickHandler = (async() => { - await unbookmark(page._id); - refreshBookmarkList(); - }); + return null; + }; + const pressEnterForRenameHandler = (async(inputText: string) => { + const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); + const newPagePath = nodePath.resolve(parentPath, inputText); + if (newPagePath === page.path) { + setRenameInputShown(false); + return; + } - const renameMenuItemClickHandler = () => { + try { + setRenameInputShown(false); + await apiv3Put('/pages/rename', { + pageId: page._id, + revisionId: page.revision, + newPagePath, + }); + refreshBookmarkList(); + toastSuccess(t('renamed_pages', { path: page.path })); + } + catch (err) { setRenameInputShown(true); - }; - - const inputValidator = (title: string | null): AlertInfo | null => { - if (title == null || title === '' || title.trim() === '') { - return { - type: AlertType.WARNING, - message: t('form_validation.title_required'), - }; - } - - return null; - }; - - const onPressEnterForRenameHandler = useCallback(async(inputText: string) => { - const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); - const newPagePath = nodePath.resolve(parentPath, inputText); - - if (newPagePath === page.path) { - setRenameInputShown(false); - return; - } - - try { - setRenameInputShown(false); - await apiv3Put('/pages/rename', { - pageId: page._id, - revisionId: page.revision, - newPagePath, - }); - refreshBookmarkList(); - toastSuccess(t('renamed_pages', { path: page.path })); - } - catch (err) { - setRenameInputShown(true); - toastError(err); - } - }, [page, t]); + toastError(err); + } + }); - return ( + return ( + <>
  • { isRenameInputShown ? ( @@ -88,7 +88,7 @@ const BookmarksItem = (props: Props) => { value={nodePath.basename(page.path ?? '')} placeholder={t('Input page name')} onClickOutside={() => { setRenameInputShown(false) }} - onPressEnter={onPressEnterForRenameHandler} + onPressEnter={pressEnterForRenameHandler} inputValidator={inputValidator} /> ) : ( @@ -101,6 +101,7 @@ const BookmarksItem = (props: Props) => { isEnableActions forceHideMenuItems={[MenuItemType.DUPLICATE]} onClickBookmarkMenuItem={bookmarkMenuItemClickHandler} + onClickRenameMenuItem={renameMenuItemClickHandler} > @@ -116,17 +117,6 @@ const BookmarksItem = (props: Props) => {
  • - ); - }); - - - return ( - <> -
      -
      - {generateBookmarkedPageList} -
      -
    ); }; @@ -159,6 +149,20 @@ const Bookmarks = () : JSX.Element => { getMyBookmarkList(); }, [getMyBookmarkList]); + const generateBookmarkList = () => { + return ( +
      +
      + { pages.map((page) => { + return ( + + ); + })} +
      +
    + ); + }; + const renderBookmarksItem = () => { if (pages?.length === 0) { return ( @@ -167,7 +171,7 @@ const Bookmarks = () : JSX.Element => { ); } - return ; + return generateBookmarkList(); }; return ( diff --git a/packages/app/src/components/Sidebar/PageTree/Item.tsx b/packages/app/src/components/Sidebar/PageTree/Item.tsx index b64308da73a..fcda82cd2cb 100644 --- a/packages/app/src/components/Sidebar/PageTree/Item.tsx +++ b/packages/app/src/components/Sidebar/PageTree/Item.tsx @@ -258,7 +258,7 @@ const Item: FC = (props: ItemProps) => { setRenameInputShown(true); }, []); - const onPressEnterForRenameHandler = async(inputText: string) => { + const pressEnterForRenameHandler = async(inputText: string) => { const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); const newPagePath = nodePath.resolve(parentPath, inputText); @@ -443,7 +443,7 @@ const Item: FC = (props: ItemProps) => { value={nodePath.basename(page.path ?? '')} placeholder={t('Input page name')} onClickOutside={() => { setRenameInputShown(false) }} - onPressEnter={onPressEnterForRenameHandler} + onPressEnter={pressEnterForRenameHandler} inputValidator={inputValidator} /> ) diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 84d328e07b5..920729237b1 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,6 +1,5 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; -import useSWRInfinite, { SWRInfiniteResponse } from 'swr/infinite'; import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -18,17 +17,3 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon }), ); }; - -export const useSWRInifiniteBookmarkedPage = (userId: string | null | undefined) : SWRInfiniteResponse => { - const getKey = (page: number) => { - return userId != null ? `/bookmarks/${userId}/?page=${page + 1}` : null; - }; - return useSWRInfinite( - getKey, - (endpoint: string) => apiv3Get<{ paginationResult }>(endpoint).then(response => response.data?.paginationResult), - { - revalidateFirstPage: false, - revalidateAll: false, - }, - ); -}; diff --git a/packages/app/src/styles/_bookmark-list.scss b/packages/app/src/styles/_bookmark-list.scss deleted file mode 100644 index f23edd79418..00000000000 --- a/packages/app/src/styles/_bookmark-list.scss +++ /dev/null @@ -1,17 +0,0 @@ -.grw-bookmarks-list{ - .list-group-item { - .grw-visible-on-hover { - display: none; - } - - &:hover { - .grw-visible-on-hover { - display: block; - } - - .grw-count-badge { - display: none; - } - } - } -} diff --git a/packages/app/src/styles/style-app.scss b/packages/app/src/styles/style-app.scss index dc70a11d3ee..7a89b2e348b 100644 --- a/packages/app/src/styles/style-app.scss +++ b/packages/app/src/styles/style-app.scss @@ -60,7 +60,6 @@ @import 'page-accessories-modal'; @import 'page-path'; @import 'page-tree'; -@import 'bookmark-list'; @import 'page'; @import 'page-presentation'; @import 'page-history'; From 73fefb600342733bac33def4c19a72523b3b9e04 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 27 Jul 2022 16:17:14 +0800 Subject: [PATCH 025/826] Remove swr from props https://youtrack.weseek.co.jp/issue/GW-7841 - Remove swr props from BookmarksItem - Implement openDeleteModal - Implement refreshBookmarkList after delete a page from bookmarks --- packages/app/src/components/Sidebar/Bookmarks.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 2361cf7a55e..dba9d48ede8 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,20 +1,19 @@ -import React, { - FC, useCallback, useEffect, useState, -} from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import nodePath from 'path'; import { DevidedPagePath, pathUtils } from '@growi/core'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; + import { unbookmark } from '~/client/services/page-operation'; import { toastError, toastSuccess } from '~/client/util/apiNotification'; +import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client'; import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; import { OnDeletedFunction } from '~/interfaces/ui'; -import { usePageDeleteModal } from '~/stores/modal'; -import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client'; import { useCurrentUser, useIsGuestUser } from '~/stores/context'; +import { usePageDeleteModal } from '~/stores/modal'; import loggerFactory from '~/utils/logger'; import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; @@ -36,7 +35,7 @@ const BookmarksItem = (props: Props) => { const dPagePath = new DevidedPagePath(page.path, false, true); const { latter: pageTitle, former: formerPagePath } = dPagePath; const bookmarkItemId = `bookmark-item-${page._id}`; - + const { open: openDeleteModal } = usePageDeleteModal(); const bookmarkMenuItemClickHandler = useCallback(async() => { await unbookmark(page._id); @@ -82,7 +81,6 @@ const BookmarksItem = (props: Props) => { } }); - const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => { const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { @@ -116,7 +114,7 @@ const BookmarksItem = (props: Props) => { }; onClickDeleteMenuItem(pageToDelete); - }, [page, openDeleteModal, t]); + }, [page, openDeleteModal, refreshBookmarkList, t]); return ( <> From d3ade25013e370df9ae8da1a4380b3f59615c995 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 27 Jul 2022 18:10:32 +0800 Subject: [PATCH 026/826] Separate BookmarkItem from Bookmarks https://youtrack.weseek.co.jp/issue/GW-7846 - Create new file for BookmarkItem in Bookmarks folder - Rename BookmarksItem to BookmarkItem - Implement BookmarkItem in Bookmarks component --- .../app/src/components/Sidebar/Bookmarks.tsx | 155 +---------------- .../Sidebar/Bookmarks/BookmarkItem.tsx | 159 ++++++++++++++++++ 2 files changed, 164 insertions(+), 150 deletions(-) create mode 100644 packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index dba9d48ede8..4c4ba023e6b 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,165 +1,20 @@ import React, { useCallback, useEffect, useState } from 'react'; -import nodePath from 'path'; - -import { DevidedPagePath, pathUtils } from '@growi/core'; import { useTranslation } from 'react-i18next'; -import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; -import { unbookmark } from '~/client/services/page-operation'; -import { toastError, toastSuccess } from '~/client/util/apiNotification'; -import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client'; -import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; -import { OnDeletedFunction } from '~/interfaces/ui'; +import { toastError } from '~/client/util/apiNotification'; +import { apiv3Get } from '~/client/util/apiv3-client'; +import { IPageHasId } from '~/interfaces/page'; import { useCurrentUser, useIsGuestUser } from '~/stores/context'; -import { usePageDeleteModal } from '~/stores/modal'; import loggerFactory from '~/utils/logger'; -import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; -import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; +import BookmarkItem from './Bookmarks/BookmarkItem'; const logger = loggerFactory('growi:BookmarkList'); // TODO: Remove pagination and apply scrolling (not infinity) const ACTIVE_PAGE = 1; -type Props = { - page: IPageHasId, - refreshBookmarkList: () => void -} - -const BookmarksItem = (props: Props) => { - const { t } = useTranslation(); - const { page, refreshBookmarkList } = props; - const [isRenameInputShown, setRenameInputShown] = useState(false); - const dPagePath = new DevidedPagePath(page.path, false, true); - const { latter: pageTitle, former: formerPagePath } = dPagePath; - const bookmarkItemId = `bookmark-item-${page._id}`; - const { open: openDeleteModal } = usePageDeleteModal(); - - const bookmarkMenuItemClickHandler = useCallback(async() => { - await unbookmark(page._id); - refreshBookmarkList(); - }, [page, refreshBookmarkList]); - - const renameMenuItemClickHandler = useCallback(() => { - setRenameInputShown(true); - }, []); - - const inputValidator = (title: string | null): AlertInfo | null => { - if (title == null || title === '' || title.trim() === '') { - return { - type: AlertType.WARNING, - message: t('form_validation.title_required'), - }; - } - - return null; - }; - - const pressEnterForRenameHandler = (async(inputText: string) => { - const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); - const newPagePath = nodePath.resolve(parentPath, inputText); - if (newPagePath === page.path) { - setRenameInputShown(false); - return; - } - - try { - setRenameInputShown(false); - await apiv3Put('/pages/rename', { - pageId: page._id, - revisionId: page.revision, - newPagePath, - }); - refreshBookmarkList(); - toastSuccess(t('renamed_pages', { path: page.path })); - } - catch (err) { - setRenameInputShown(true); - toastError(err); - } - }); - - const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { - const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => { - const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { - if (typeof pathOrPathsToDelete !== 'string') { - return; - } - const path = pathOrPathsToDelete; - - if (isCompletely) { - toastSuccess(t('deleted_pages_completely', { path })); - } - else { - toastSuccess(t('deleted_pages', { path })); - } - refreshBookmarkList(); - }; - openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); - }; - - if (page._id == null || page.path == null) { - throw Error('_id and path must not be null.'); - } - - const pageToDelete: IPageToDeleteWithMeta = { - data: { - _id: page._id, - revision: page.revision as string, - path: page.path, - }, - meta: pageInfo, - }; - - onClickDeleteMenuItem(pageToDelete); - }, [page, openDeleteModal, refreshBookmarkList, t]); - - return ( - <> -
    -
  • - { isRenameInputShown ? ( - { setRenameInputShown(false) }} - onPressEnter={pressEnterForRenameHandler} - inputValidator={inputValidator} - /> - ) : ( - -

    {pageTitle}

    -
    - )} - - - - - - - { formerPagePath || '/' } - -
  • -
    - - ); -}; - - const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: currentUser } = useCurrentUser(); @@ -193,7 +48,7 @@ const Bookmarks = () : JSX.Element => {
    { pages.map((page) => { return ( - + ); })}
    diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx new file mode 100644 index 00000000000..9ee464391f3 --- /dev/null +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx @@ -0,0 +1,159 @@ + +import React, { useCallback, useState } from 'react'; + +import nodePath from 'path'; + + +import { DevidedPagePath, pathUtils } from '@growi/core'; +import { useTranslation } from 'react-i18next'; +import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; + +import { unbookmark } from '~/client/services/page-operation'; +import { toastError, toastSuccess } from '~/client/util/apiNotification'; +import { apiv3Put } from '~/client/util/apiv3-client'; +import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; +import { OnDeletedFunction } from '~/interfaces/ui'; +import { usePageDeleteModal } from '~/stores/modal'; + + +import ClosableTextInput, { AlertInfo, AlertType } from '../../Common/ClosableTextInput'; +import { MenuItemType, PageItemControl } from '../../Common/Dropdown/PageItemControl'; + + +type Props = { + page: IPageHasId, + refreshBookmarkList: () => void +} + +const BookmarkItem = (props: Props): JSX.Element => { + const { t } = useTranslation(); + const { page, refreshBookmarkList } = props; + const [isRenameInputShown, setRenameInputShown] = useState(false); + const dPagePath = new DevidedPagePath(page.path, false, true); + const { latter: pageTitle, former: formerPagePath } = dPagePath; + const bookmarkItemId = `bookmark-item-${page._id}`; + const { open: openDeleteModal } = usePageDeleteModal(); + + const bookmarkMenuItemClickHandler = useCallback(async() => { + await unbookmark(page._id); + refreshBookmarkList(); + }, [page, refreshBookmarkList]); + + const renameMenuItemClickHandler = useCallback(() => { + setRenameInputShown(true); + }, []); + + const inputValidator = (title: string | null): AlertInfo | null => { + if (title == null || title === '' || title.trim() === '') { + return { + type: AlertType.WARNING, + message: t('form_validation.title_required'), + }; + } + + return null; + }; + + const pressEnterForRenameHandler = (async(inputText: string) => { + const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); + const newPagePath = nodePath.resolve(parentPath, inputText); + if (newPagePath === page.path) { + setRenameInputShown(false); + return; + } + + try { + setRenameInputShown(false); + await apiv3Put('/pages/rename', { + pageId: page._id, + revisionId: page.revision, + newPagePath, + }); + refreshBookmarkList(); + toastSuccess(t('renamed_pages', { path: page.path })); + } + catch (err) { + setRenameInputShown(true); + toastError(err); + } + }); + + const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { + const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => { + const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + if (typeof pathOrPathsToDelete !== 'string') { + return; + } + const path = pathOrPathsToDelete; + + if (isCompletely) { + toastSuccess(t('deleted_pages_completely', { path })); + } + else { + toastSuccess(t('deleted_pages', { path })); + } + refreshBookmarkList(); + }; + openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); + }; + + if (page._id == null || page.path == null) { + throw Error('_id and path must not be null.'); + } + + const pageToDelete: IPageToDeleteWithMeta = { + data: { + _id: page._id, + revision: page.revision as string, + path: page.path, + }, + meta: pageInfo, + }; + + onClickDeleteMenuItem(pageToDelete); + }, [page, openDeleteModal, refreshBookmarkList, t]); + + return ( + <> +
    +
  • + { isRenameInputShown ? ( + { setRenameInputShown(false) }} + onPressEnter={pressEnterForRenameHandler} + inputValidator={inputValidator} + /> + ) : ( + +

    {pageTitle}

    +
    + )} + + + + + + + { formerPagePath || '/' } + +
  • +
    + + ); +}; + +export default BookmarkItem; From 08ac0f3e42d5d0b7cfa82f20c78564f28fd08d24 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 28 Jul 2022 13:56:57 +0800 Subject: [PATCH 027/826] Fix tooltip behavior https://youtrack.weseek.co.jp/issue/GW-7847 - Disable fade transition of UncontrolledTooltip --- packages/app/src/components/Sidebar/Bookmarks.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index b2cddbe44ba..83efe3ccaf1 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -51,6 +51,7 @@ const BookmarksItem = (props: Props) => { autohide={false} placement="right" target={bookmarkItemId} + fade={false} > { formerPagePath || '/' } From 3a4996c4b325c577d7040852e77237f7cf735f29 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 28 Jul 2022 18:36:08 +0800 Subject: [PATCH 028/826] Use SWR for current user bookmarks https://youtrack.weseek.co.jp/issue/GW-7839 - Add SWR method for bookmarks item - Set page as optional parameter in useSWRCurrentUserBookmarks - Implement SWR to get pages data in Bookmark component - Remove refreshBookmarkList props from Bookmark component - Call mutateCurrentUserBookmark from PageListItemL and SubNavButtons --- .../src/components/Navbar/SubNavButtons.tsx | 6 ++- .../src/components/PageList/PageListItemL.tsx | 4 +- .../app/src/components/Sidebar/Bookmarks.tsx | 43 ++++--------------- packages/app/src/stores/bookmark.ts | 19 ++++++++ 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/packages/app/src/components/Navbar/SubNavButtons.tsx b/packages/app/src/components/Navbar/SubNavButtons.tsx index ed08a49fe9c..4c6c5df2116 100644 --- a/packages/app/src/components/Navbar/SubNavButtons.tsx +++ b/packages/app/src/components/Navbar/SubNavButtons.tsx @@ -7,7 +7,7 @@ import { import { useIsGuestUser } from '~/stores/context'; import { IPageForPageDuplicateModal } from '~/stores/modal'; -import { useSWRBookmarkInfo } from '../../stores/bookmark'; +import { useSWRBookmarkInfo, useSWRCurrentUserBookmarks } from '../../stores/bookmark'; import { useSWRxPageInfo } from '../../stores/page'; import { useSWRxUsersList } from '../../stores/user'; import BookmarkButtons from '../BookmarkButtons'; @@ -51,6 +51,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId, shareLinkId); const { data: bookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(pageId); + const { mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmarks(); const likerIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.likerIds ?? []).slice(0, 15) : []; const seenUserIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.seenUserIds ?? []).slice(0, 15) : []; @@ -95,7 +96,8 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element await toggleBookmark(pageId, pageInfo.isBookmarked); mutatePageInfo(); mutateBookmarkInfo(); - }, [isGuestUser, mutateBookmarkInfo, mutatePageInfo, pageId, pageInfo]); + mutateCurrentUserBookmark(); + }, [isGuestUser, mutateBookmarkInfo, mutatePageInfo, mutateCurrentUserBookmark, pageId, pageInfo]); const duplicateMenuItemClickHandler = useCallback(async(_pageId: string): Promise => { if (onClickDuplicateMenuItem == null || path == null) { diff --git a/packages/app/src/components/PageList/PageListItemL.tsx b/packages/app/src/components/PageList/PageListItemL.tsx index 9e7567828e3..47a9baf43ec 100644 --- a/packages/app/src/components/PageList/PageListItemL.tsx +++ b/packages/app/src/components/PageList/PageListItemL.tsx @@ -23,6 +23,7 @@ import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction, } from '~/interfaces/ui'; import LinkedPagePath from '~/models/linked-page-path'; +import { useSWRCurrentUserBookmarks } from '~/stores/bookmark'; import { usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageModal, } from '~/stores/modal'; @@ -85,7 +86,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction = (pr const shouldFetch = isSelected && (pageData != null || pageMeta != null); const { data: pageInfo } = useSWRxPageInfo(shouldFetch ? pageData?._id : null); - + const { mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmarks(); const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null; const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null; @@ -122,6 +123,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction = (pr const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise => { const bookmarkOperation = _newValue ? bookmark : unbookmark; await bookmarkOperation(_pageId); + mutateCurrentUserBookmark(); }; const duplicateMenuItemClickHandler = useCallback(() => { diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 28ce109edc3..650369d3794 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,30 +1,25 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { DevidedPagePath } from '@growi/core'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import { unbookmark } from '~/client/services/page-operation'; -import { toastError } from '~/client/util/apiNotification'; -import { apiv3Get } from '~/client/util/apiv3-client'; import { IPageHasId } from '~/interfaces/page'; -import { useCurrentUser, useIsGuestUser } from '~/stores/context'; -import loggerFactory from '~/utils/logger'; +import { useSWRCurrentUserBookmarks } from '~/stores/bookmark'; +import { useIsGuestUser } from '~/stores/context'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; -const logger = loggerFactory('growi:BookmarkList'); -// TODO: Remove pagination and apply scrolling (not infinity) -const ACTIVE_PAGE = 1; type Props = { pages: IPageHasId[] | undefined, - refreshBookmarkList: () => void } const BookmarksItem = (props: Props) => { - const { pages, refreshBookmarkList } = props; + const { pages } = props; + const { mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmarks(); const generateBookmarkedPageList = pages?.map((page) => { const dPagePath = new DevidedPagePath(page.path, false, true); @@ -33,7 +28,7 @@ const BookmarksItem = (props: Props) => { const bookmarkMenuItemClickHandler = (async() => { await unbookmark(page._id); - refreshBookmarkList(); + mutateCurrentUserBookmark(); }); @@ -81,30 +76,8 @@ const BookmarksItem = (props: Props) => { const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); - const { data: currentUser } = useCurrentUser(); const { data: isGuestUser } = useIsGuestUser(); - const [pages, setPages] = useState([]); - const page = ACTIVE_PAGE; - - const getMyBookmarkList = useCallback(async() => { - try { - const res = await apiv3Get(`/bookmarks/${currentUser?._id}`, { page }); - const { paginationResult } = res.data; - setPages(paginationResult.docs.map((page) => { - return { - ...page.page, - }; - })); - } - catch (error) { - logger.error('failed to fetch data', error); - toastError(error, 'Error occurred in bookmark page list'); - } - }, [currentUser, page]); - - useEffect(() => { - getMyBookmarkList(); - }, [getMyBookmarkList]); + const { data: pages } = useSWRCurrentUserBookmarks(); const renderBookmarksItem = () => { if (pages?.length === 0) { @@ -114,7 +87,7 @@ const Bookmarks = () : JSX.Element => { ); } - return ; + return ; }; return ( diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 920729237b1..38dab022503 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,9 +1,12 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; +import { IPageHasId } from '~/interfaces/page'; + import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; +import { useCurrentUser } from './context'; export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRResponse => { return useSWRImmutable( @@ -17,3 +20,19 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon }), ); }; + +export const useSWRCurrentUserBookmarks = (page?: number| null| undefined): SWRResponse => { + const { data: currentUser } = useCurrentUser(); + const currentPage = page ?? 1; + return useSWRImmutable( + currentUser != null ? `/bookmarks/${currentUser._id}` : null, + endpoint => apiv3Get(endpoint, { page: currentPage }).then((response) => { + const { paginationResult } = response.data; + return paginationResult.docs.map((item) => { + return { + ...item.page, + }; + }); + }), + ); +}; From ad1ec7b373e59445e8cc494d61028aefe227db95 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 29 Jul 2022 18:46:46 +0800 Subject: [PATCH 029/826] Rename variable https://youtrack.weseek.co.jp/issue/GW-7839 - Rename function useSWRCurrentUserBookmarks to useSWRxCurrentUserBookmarks - Rename parameter page to pageNum - Implement Nullable argument type for pageNum - Update implementation of useSWRxCurrentUserBookmarks - Add trailing slash to tooltip content Reference: https://github.com/weseek/growi/pull/6292#discussion_r927273549 - Fix bookmarklist not updated updating after accessing other sidebar content --- .../app/src/components/Navbar/SubNavButtons.tsx | 4 ++-- .../src/components/PageList/PageListItemL.tsx | 4 ++-- .../app/src/components/Sidebar/Bookmarks.tsx | 17 +++++++++++------ packages/app/src/stores/bookmark.ts | 5 +++-- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/Navbar/SubNavButtons.tsx b/packages/app/src/components/Navbar/SubNavButtons.tsx index 4c6c5df2116..03525f21e4b 100644 --- a/packages/app/src/components/Navbar/SubNavButtons.tsx +++ b/packages/app/src/components/Navbar/SubNavButtons.tsx @@ -7,7 +7,7 @@ import { import { useIsGuestUser } from '~/stores/context'; import { IPageForPageDuplicateModal } from '~/stores/modal'; -import { useSWRBookmarkInfo, useSWRCurrentUserBookmarks } from '../../stores/bookmark'; +import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '../../stores/bookmark'; import { useSWRxPageInfo } from '../../stores/page'; import { useSWRxUsersList } from '../../stores/user'; import BookmarkButtons from '../BookmarkButtons'; @@ -51,7 +51,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId, shareLinkId); const { data: bookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(pageId); - const { mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmarks(); + const { mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks(); const likerIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.likerIds ?? []).slice(0, 15) : []; const seenUserIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.seenUserIds ?? []).slice(0, 15) : []; diff --git a/packages/app/src/components/PageList/PageListItemL.tsx b/packages/app/src/components/PageList/PageListItemL.tsx index 47a9baf43ec..412ff76333a 100644 --- a/packages/app/src/components/PageList/PageListItemL.tsx +++ b/packages/app/src/components/PageList/PageListItemL.tsx @@ -23,7 +23,7 @@ import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction, } from '~/interfaces/ui'; import LinkedPagePath from '~/models/linked-page-path'; -import { useSWRCurrentUserBookmarks } from '~/stores/bookmark'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageModal, } from '~/stores/modal'; @@ -86,7 +86,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction = (pr const shouldFetch = isSelected && (pageData != null || pageMeta != null); const { data: pageInfo } = useSWRxPageInfo(shouldFetch ? pageData?._id : null); - const { mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmarks(); + const { mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks(); const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null; const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null; diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 650369d3794..eb6b5307bd3 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,13 +1,13 @@ import React, { useEffect } from 'react'; -import { DevidedPagePath } from '@growi/core'; +import { DevidedPagePath, pathUtils } from '@growi/core'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; import { unbookmark } from '~/client/services/page-operation'; import { IPageHasId } from '~/interfaces/page'; -import { useSWRCurrentUserBookmarks } from '~/stores/bookmark'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useIsGuestUser } from '~/stores/context'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; @@ -19,11 +19,12 @@ type Props = { const BookmarksItem = (props: Props) => { const { pages } = props; - const { mutate: mutateCurrentUserBookmark } = useSWRCurrentUserBookmarks(); + const { mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks(); const generateBookmarkedPageList = pages?.map((page) => { const dPagePath = new DevidedPagePath(page.path, false, true); - const { latter: pageTitle, former: formerPagePath } = dPagePath; + const { latter: pageTitle, former, isRoot } = dPagePath; + const formerPagePath = isRoot ? pageTitle : pathUtils.addTrailingSlash(former); const bookmarkItemId = `bookmark-item-${page._id}`; const bookmarkMenuItemClickHandler = (async() => { @@ -54,7 +55,7 @@ const BookmarksItem = (props: Props) => { placement="right" target={bookmarkItemId} > - { formerPagePath || '/' } + { formerPagePath }
    @@ -77,7 +78,11 @@ const BookmarksItem = (props: Props) => { const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); - const { data: pages } = useSWRCurrentUserBookmarks(); + const { data: pages, mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks(); + + useEffect(() => { + mutateCurrentUserBookmark(); + }, [mutateCurrentUserBookmark]); const renderBookmarksItem = () => { if (pages?.length === 0) { diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 38dab022503..aa99ca0feae 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,6 +1,7 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; +import { Nullable } from '~/interfaces/common'; import { IPageHasId } from '~/interfaces/page'; import { apiv3Get } from '../client/util/apiv3-client'; @@ -21,9 +22,9 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon ); }; -export const useSWRCurrentUserBookmarks = (page?: number| null| undefined): SWRResponse => { +export const useSWRxCurrentUserBookmarks = (pageNum?: Nullable): SWRResponse => { const { data: currentUser } = useCurrentUser(); - const currentPage = page ?? 1; + const currentPage = pageNum ?? 1; return useSWRImmutable( currentUser != null ? `/bookmarks/${currentUser._id}` : null, endpoint => apiv3Get(endpoint, { page: currentPage }).then((response) => { From 8f01f2e779f8823228b1b098ff82ac3f395a1239 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 29 Jul 2022 19:05:29 +0800 Subject: [PATCH 030/826] Reload bookmark list from pageTree https://youtrack.weseek.co.jp/issue/GW-7839 - Remove useEffect from Bookmarks component - Implement mutateCurrentUserBookmarks from page tree item - Move bookmarkMenuItemClickHandler method in Page Tree Item --- packages/app/src/components/Sidebar/Bookmarks.tsx | 8 ++------ .../app/src/components/Sidebar/PageTree/Item.tsx | 14 ++++++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index eb6b5307bd3..1746530fd56 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,5 +1,5 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { DevidedPagePath, pathUtils } from '@growi/core'; import { useTranslation } from 'react-i18next'; @@ -78,11 +78,7 @@ const BookmarksItem = (props: Props) => { const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); - const { data: pages, mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks(); - - useEffect(() => { - mutateCurrentUserBookmark(); - }, [mutateCurrentUserBookmark]); + const { data: pages } = useSWRxCurrentUserBookmarks(); const renderBookmarksItem = () => { if (pages?.length === 0) { diff --git a/packages/app/src/components/Sidebar/PageTree/Item.tsx b/packages/app/src/components/Sidebar/PageTree/Item.tsx index b64308da73a..e77c7846ee8 100644 --- a/packages/app/src/components/Sidebar/PageTree/Item.tsx +++ b/packages/app/src/components/Sidebar/PageTree/Item.tsx @@ -16,6 +16,7 @@ import TriangleIcon from '~/components/Icons/TriangleIcon'; import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta, } from '~/interfaces/page'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { IPageForPageDuplicateModal } from '~/stores/modal'; import { useSWRxPageChildren } from '~/stores/page-listing'; import { usePageTreeDescCountMap } from '~/stores/ui'; @@ -57,12 +58,6 @@ const markTarget = (children: ItemNode[], targetPathOrId?: string): void => { }); }; - -const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise => { - const bookmarkOperation = _newValue ? bookmark : unbookmark; - await bookmarkOperation(_pageId); -}; - /** * Return new page path after the droppedPagePath is moved under the newParentPagePath * @param droppedPagePath @@ -111,6 +106,7 @@ const Item: FC = (props: ItemProps) => { const [isCreating, setCreating] = useState(false); const { data, mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null); + const { mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); // descendantCount const { getDescCount } = usePageTreeDescCountMap(); @@ -238,6 +234,12 @@ const Item: FC = (props: ItemProps) => { } }, [hasDescendants]); + const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise => { + const bookmarkOperation = _newValue ? bookmark : unbookmark; + await bookmarkOperation(_pageId); + mutateCurrentUserBookmarks(); + }; + const duplicateMenuItemClickHandler = useCallback((): void => { if (onClickDuplicateMenuItem == null) { return; From 07c3343d101212b98b3bb493f3073bf345bfeff2 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 1 Aug 2022 12:38:53 +0800 Subject: [PATCH 031/826] Code improvement https://youtrack.weseek.co.jp/issue/GW-7839 - Rename BookmarkItem props of pages to currentUserBookmarksData - Add props onPageOperationSuccess to BookmarkItem - Rename BookmarksItem to BookmarkItem - Rename mutateCurrentUserBookmark to mutateCurrentUserBookmarks - Call mutation by passing props in Bookmarks component --- .../app/src/components/Sidebar/Bookmarks.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 1746530fd56..7a7de269f15 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -14,14 +14,14 @@ import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemContro type Props = { - pages: IPageHasId[] | undefined, + currentUserBookmarksData: IPageHasId[] | undefined, + onPageOperationSuccess: () => void } -const BookmarksItem = (props: Props) => { - const { pages } = props; - const { mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks(); +const BookmarkItem = (props: Props) => { + const { currentUserBookmarksData, onPageOperationSuccess } = props; - const generateBookmarkedPageList = pages?.map((page) => { + const generateBookmarkedPageList = currentUserBookmarksData?.map((page) => { const dPagePath = new DevidedPagePath(page.path, false, true); const { latter: pageTitle, former, isRoot } = dPagePath; const formerPagePath = isRoot ? pageTitle : pathUtils.addTrailingSlash(former); @@ -29,7 +29,7 @@ const BookmarksItem = (props: Props) => { const bookmarkMenuItemClickHandler = (async() => { await unbookmark(page._id); - mutateCurrentUserBookmark(); + onPageOperationSuccess(); }); @@ -78,17 +78,17 @@ const BookmarksItem = (props: Props) => { const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); - const { data: pages } = useSWRxCurrentUserBookmarks(); + const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); const renderBookmarksItem = () => { - if (pages?.length === 0) { + if (currentUserBookmarksData?.length === 0) { return ( -

    +

    { t('No bookmarks yet') } -

    + ); } - return ; + return ; }; return ( @@ -99,9 +99,9 @@ const Bookmarks = () : JSX.Element => { { isGuestUser ? ( -

    +

    { t('Not available for guest') } -

    + ) : renderBookmarksItem() } From 0747891de78fbde837bdf2683b00260c462e8f78 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 1 Aug 2022 15:18:24 +0800 Subject: [PATCH 032/826] Code improvement https://youtrack.weseek.co.jp/issue/GW-7839 - Rename currentUserBookmarksData props to bookmarkedPage - Change type of bookmarkedPage - Return BookmarkItem as single element - Add method to generate mapped component of BookmarkItem --- .../app/src/components/Sidebar/Bookmarks.tsx | 104 +++++++++--------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 7a7de269f15..ccef9db8b13 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,5 +1,5 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { DevidedPagePath, pathUtils } from '@growi/core'; import { useTranslation } from 'react-i18next'; @@ -14,63 +14,50 @@ import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemContro type Props = { - currentUserBookmarksData: IPageHasId[] | undefined, + bookmarkedPage: IPageHasId, onPageOperationSuccess: () => void } const BookmarkItem = (props: Props) => { - const { currentUserBookmarksData, onPageOperationSuccess } = props; + const { bookmarkedPage, onPageOperationSuccess } = props; - const generateBookmarkedPageList = currentUserBookmarksData?.map((page) => { - const dPagePath = new DevidedPagePath(page.path, false, true); - const { latter: pageTitle, former, isRoot } = dPagePath; - const formerPagePath = isRoot ? pageTitle : pathUtils.addTrailingSlash(former); - const bookmarkItemId = `bookmark-item-${page._id}`; + const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true); + const { latter: pageTitle, former, isRoot } = dPagePath; + const formerPagePath = isRoot ? pageTitle : pathUtils.addTrailingSlash(former); + const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`; - const bookmarkMenuItemClickHandler = (async() => { - await unbookmark(page._id); - onPageOperationSuccess(); - }); - - - return ( -
    -
  • - -

    {pageTitle}

    -
    - - - - - - - { formerPagePath } - -
  • -
    - ); - }); + const bookmarkMenuItemClickHandler = useCallback(async() => { + await unbookmark(bookmarkedPage._id); + onPageOperationSuccess(); + }, [onPageOperationSuccess, bookmarkedPage]); return ( - <> -
      -
      - {generateBookmarkedPageList} -
      -
    - +
    +
  • + +

    {pageTitle}

    +
    + + + + + + + { formerPagePath } + +
  • +
    ); }; @@ -80,6 +67,20 @@ const Bookmarks = () : JSX.Element => { const { data: isGuestUser } = useIsGuestUser(); const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); + const generateBookmarkList = () => { + return ( +
      +
      + { currentUserBookmarksData?.map((currentUserBookmark) => { + return ( + + ); + })} +
      +
    + ); + }; + const renderBookmarksItem = () => { if (currentUserBookmarksData?.length === 0) { return ( @@ -88,7 +89,7 @@ const Bookmarks = () : JSX.Element => { ); } - return ; + return generateBookmarkList(); }; return ( @@ -96,7 +97,6 @@ const Bookmarks = () : JSX.Element => {

    {t('Bookmarks')}

    - { isGuestUser ? (

    @@ -104,10 +104,8 @@ const Bookmarks = () : JSX.Element => {

    ) : renderBookmarksItem() } - ); - }; export default Bookmarks; From 11a69afae7d881b02af210be6bd3f4054352850a Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 1 Aug 2022 15:23:10 +0800 Subject: [PATCH 033/826] Update Bookmarks.tsx --- packages/app/src/components/Sidebar/Bookmarks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 697ee3abc92..4798a67175e 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -30,7 +30,7 @@ const BookmarkItem = (props: Props) => { await unbookmark(bookmarkedPage._id); onPageOperationSuccess(); }, [onPageOperationSuccess, bookmarkedPage]); - + return (
  • From 7bc01f044729b01b529e4bec0fe197d28b510181 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 3 Aug 2022 15:43:44 +0800 Subject: [PATCH 034/826] Update render condition https://youtrack.weseek.co.jp/issue/GW-7839 - Update render condition of renderBookmarItem - Remove generateBookmarkList method - Rename renderBookmarkItem to renderBookmarkList --- .../app/src/components/Sidebar/Bookmarks.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 4798a67175e..2164dfb848a 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -65,7 +65,14 @@ const Bookmarks = () : JSX.Element => { const { data: isGuestUser } = useIsGuestUser(); const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); - const generateBookmarkList = () => { + const renderBookmarkList = () => { + if (currentUserBookmarksData?.length === 0) { + return ( +

    + { t('No bookmarks yet') } +

    + ); + } return (
      @@ -79,17 +86,6 @@ const Bookmarks = () : JSX.Element => { ); }; - const renderBookmarksItem = () => { - if (currentUserBookmarksData?.length === 0) { - return ( -

      - { t('No bookmarks yet') } -

      - ); - } - return generateBookmarkList(); - }; - return ( <>
      @@ -100,7 +96,7 @@ const Bookmarks = () : JSX.Element => {

      { t('Not available for guest') }

      - ) : renderBookmarksItem() + ) : renderBookmarkList() } ); From 1b24af7d65b425e6b1a7b6d03f316a758d29a5e9 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 3 Aug 2022 16:17:08 +0800 Subject: [PATCH 035/826] Implement useCallback https://youtrack.weseek.co.jp/issue/GW-7840 - Implement useCallback for pressEnterForRenameHandler - Import missing useState --- packages/app/src/components/Sidebar/Bookmarks.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 7b4fcd2b044..51808eade55 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,5 +1,5 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import nodePath from 'path'; @@ -52,7 +52,7 @@ const BookmarkItem = (props: Props) => { return null; }; - const pressEnterForRenameHandler = (async(inputText: string) => { + const pressEnterForRenameHandler = useCallback(async(inputText: string) => { const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(bookmarkedPage.path ?? '')); const newPagePath = nodePath.resolve(parentPath, inputText); if (newPagePath === bookmarkedPage.path) { @@ -74,7 +74,7 @@ const BookmarkItem = (props: Props) => { setRenameInputShown(true); toastError(err); } - }); + }, [bookmarkedPage, onPageOperationSuccess, t]); return (
      From d782b46d22efe85b86479609be8cdd5a04786afe Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 3 Aug 2022 16:33:21 +0800 Subject: [PATCH 036/826] Remove unused styling and modify route https://youtrack.weseek.co.jp/issue/GW-7840 - Remove offset options of bookmarks route - Remove unused styling - Add fade options to UncontrolledTooltip (Accidentally deleted on previous PR) --- packages/app/src/components/Sidebar/Bookmarks.tsx | 1 + packages/app/src/server/routes/apiv3/bookmarks.js | 2 -- packages/app/src/styles/theme/_apply-colors.scss | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 51808eade55..93ca3419f69 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -108,6 +108,7 @@ const BookmarkItem = (props: Props) => { autohide={false} placement="right" target={bookmarkItemId} + fade={false} > { formerPagePath } diff --git a/packages/app/src/server/routes/apiv3/bookmarks.js b/packages/app/src/server/routes/apiv3/bookmarks.js index a895afceefc..39df6d67d8b 100644 --- a/packages/app/src/server/routes/apiv3/bookmarks.js +++ b/packages/app/src/server/routes/apiv3/bookmarks.js @@ -201,7 +201,6 @@ module.exports = (crowi) => { const { userId } = req.params; const page = req.query.page; const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30; - const offset = page > 0 ? (page - 1) * limit : page; if (userId == null) { return res.apiv3Err('User id is not found or forbidden', 400); @@ -223,7 +222,6 @@ module.exports = (crowi) => { model: 'User', }, }, - offset, page, limit, }, diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index f069d3995c2..c8a51c158dc 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -20,7 +20,6 @@ $bgcolor-page-list-group-item-active: lighten($primary, 76%) !default; $color-page-list-group-item-meta: $gray-500 !default; $color-search-page-list-title: $color-global !default; $bgcolor-subnav: darken($bgcolor-global, 3%) !default; -$bgcolor-list-hover: darken($primary, 8%) !default; // override bootstrap variables $body-bg: $bgcolor-global; From de4ef317a17d99776aea051f638499d5d904c96a Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 3 Aug 2022 16:54:09 +0800 Subject: [PATCH 037/826] Update Bookmarks.tsx --- packages/app/src/components/Sidebar/Bookmarks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index d6b8703c3d4..bae0924487d 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -12,10 +12,10 @@ import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Put } from '~/client/util/apiv3-client'; import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; import { OnDeletedFunction } from '~/interfaces/ui'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useIsGuestUser } from '~/stores/context'; import { usePageDeleteModal } from '~/stores/modal'; -import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; From 6435031f4033dd183dfadfbc079dd5b9b1c53106 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 3 Aug 2022 18:00:57 +0800 Subject: [PATCH 038/826] Separate props https://youtrack.weseek.co.jp/issue/GW-7840 - Separate props of BookmarkItem --- .../app/src/components/Sidebar/Bookmarks.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 93ca3419f69..d3e183f83e9 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -20,11 +20,12 @@ import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemContro type Props = { bookmarkedPage: IPageHasId, - onPageOperationSuccess: () => void + onUnbookmarked: () => void, + onRenamed: () => void } const BookmarkItem = (props: Props) => { - const { bookmarkedPage, onPageOperationSuccess } = props; + const { bookmarkedPage, onUnbookmarked, onRenamed } = props; const { t } = useTranslation(); const [isRenameInputShown, setRenameInputShown] = useState(false); const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true); @@ -34,8 +35,8 @@ const BookmarkItem = (props: Props) => { const bookmarkMenuItemClickHandler = useCallback(async() => { await unbookmark(bookmarkedPage._id); - onPageOperationSuccess(); - }, [onPageOperationSuccess, bookmarkedPage]); + onUnbookmarked(); + }, [onUnbookmarked, bookmarkedPage]); const renameMenuItemClickHandler = useCallback(() => { setRenameInputShown(true); @@ -67,14 +68,14 @@ const BookmarkItem = (props: Props) => { revisionId: bookmarkedPage.revision, newPagePath, }); - onPageOperationSuccess(); + onRenamed(); toastSuccess(t('renamed_pages', { path: bookmarkedPage.path })); } catch (err) { setRenameInputShown(true); toastError(err); } - }, [bookmarkedPage, onPageOperationSuccess, t]); + }, [bookmarkedPage, onRenamed, t]); return (
      @@ -135,7 +136,12 @@ const Bookmarks = () : JSX.Element => {
      { currentUserBookmarksData?.map((currentUserBookmark) => { return ( - + ); })}
      From aaa03c820dcfd0faee318044470d0a88e7f5f893 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 3 Aug 2022 18:08:12 +0800 Subject: [PATCH 039/826] Adjust props https://youtrack.weseek.co.jp/issue/GW-7841 - Add and implement onDeleted props --- packages/app/src/components/Sidebar/Bookmarks.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 8628803ddf7..6dc987cb64b 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -24,11 +24,14 @@ import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemContro type Props = { bookmarkedPage: IPageHasId, onUnbookmarked: () => void, - onRenamed: () => void + onRenamed: () => void, + onDeleted: () => void } const BookmarkItem = (props: Props) => { - const { bookmarkedPage, onUnbookmarked, onRenamed } = props; + const { + bookmarkedPage, onUnbookmarked, onRenamed, onDeleted, + } = props; const { t } = useTranslation(); const [isRenameInputShown, setRenameInputShown] = useState(false); const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true); @@ -95,7 +98,7 @@ const BookmarkItem = (props: Props) => { else { toastSuccess(t('deleted_pages', { path })); } - onPageOperationSuccess(); + onDeleted(); }; openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); }; @@ -114,7 +117,7 @@ const BookmarkItem = (props: Props) => { }; onClickDeleteMenuItem(pageToDelete); - }, [bookmarkedPage, openDeleteModal, onPageOperationSuccess, t]); + }, [bookmarkedPage, openDeleteModal, onDeleted, t]); return (
      @@ -181,6 +184,7 @@ const Bookmarks = () : JSX.Element => { bookmarkedPage={currentUserBookmark} onUnbookmarked={mutateCurrentUserBookmarks} onRenamed={mutateCurrentUserBookmarks} + onDeleted={mutateCurrentUserBookmarks} /> ); })} From d446bf1b0a210da781cd61e1a6f9cc637b15b144 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 4 Aug 2022 16:06:59 +0800 Subject: [PATCH 040/826] Fix bookmark list not updated after delete https://youtrack.weseek.co.jp/issue/GW-7841 - Move delete bookmarked page method to Bookmarks component --- .../app/src/components/Sidebar/Bookmarks.tsx | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 6dc987cb64b..79632a1b3e4 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -25,7 +25,7 @@ type Props = { bookmarkedPage: IPageHasId, onUnbookmarked: () => void, onRenamed: () => void, - onDeleted: () => void + onDeleted: (pageToDelete: IPageToDeleteWithMeta) => void } const BookmarkItem = (props: Props) => { @@ -38,7 +38,6 @@ const BookmarkItem = (props: Props) => { const { latter: pageTitle, former, isRoot } = dPagePath; const formerPagePath = isRoot ? pageTitle : pathUtils.addTrailingSlash(former); const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`; - const { open: openDeleteModal } = usePageDeleteModal(); const bookmarkMenuItemClickHandler = useCallback(async() => { await unbookmark(bookmarkedPage._id); @@ -85,24 +84,6 @@ const BookmarkItem = (props: Props) => { }, [bookmarkedPage, onRenamed, t]); const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { - const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => { - const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { - if (typeof pathOrPathsToDelete !== 'string') { - return; - } - const path = pathOrPathsToDelete; - - if (isCompletely) { - toastSuccess(t('deleted_pages_completely', { path })); - } - else { - toastSuccess(t('deleted_pages', { path })); - } - onDeleted(); - }; - openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); - }; - if (bookmarkedPage._id == null || bookmarkedPage.path == null) { throw Error('_id and path must not be null.'); } @@ -116,8 +97,8 @@ const BookmarkItem = (props: Props) => { meta: pageInfo, }; - onClickDeleteMenuItem(pageToDelete); - }, [bookmarkedPage, openDeleteModal, onDeleted, t]); + onDeleted(pageToDelete); + }, [bookmarkedPage, onDeleted]); return (
      @@ -165,6 +146,25 @@ const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); + const { open: openDeleteModal } = usePageDeleteModal(); + + const onBookmarkItemDeleted = (pageToDelete: IPageToDeleteWithMeta):void => { + const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + if (typeof pathOrPathsToDelete !== 'string') { + return; + } + const path = pathOrPathsToDelete; + + if (isCompletely) { + toastSuccess(t('deleted_pages_completely', { path })); + } + else { + toastSuccess(t('deleted_pages', { path })); + } + mutateCurrentUserBookmarks(); + }; + openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); + }; const renderBookmarkList = () => { if (currentUserBookmarksData?.length === 0) { @@ -184,7 +184,7 @@ const Bookmarks = () : JSX.Element => { bookmarkedPage={currentUserBookmark} onUnbookmarked={mutateCurrentUserBookmarks} onRenamed={mutateCurrentUserBookmarks} - onDeleted={mutateCurrentUserBookmarks} + onDeleted={onBookmarkItemDeleted} /> ); })} From 046a46df0811d97a3e05eaa1204f36dd4e6c464d Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 4 Aug 2022 16:40:29 +0800 Subject: [PATCH 041/826] Code improvement https://youtrack.weseek.co.jp/issue/GW-7846 - Remove unused imports - Adjust Props type - Adjust variable and params name --- .../app/src/components/Sidebar/Bookmarks.tsx | 7 +- .../Sidebar/Bookmarks/BookmarkItem.tsx | 147 ++++++++---------- 2 files changed, 68 insertions(+), 86 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 93cbfc6e677..5c26241ae3d 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -2,12 +2,13 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; + import { toastSuccess } from '~/client/util/apiNotification'; import { IPageToDeleteWithMeta } from '~/interfaces/page'; +import { OnDeletedFunction } from '~/interfaces/ui'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useIsGuestUser } from '~/stores/context'; import { usePageDeleteModal } from '~/stores/modal'; -import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; -import { OnDeletedFunction } from '~/interfaces/ui'; import BookmarkItem from './Bookmarks/BookmarkItem'; @@ -19,7 +20,7 @@ const Bookmarks = () : JSX.Element => { const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); const { open: openDeleteModal } = usePageDeleteModal(); - const onBookmarkItemDeleted = (pageToDelete: IPageToDeleteWithMeta):void => { + const onBookmarkItemDeleted = (pageToDelete: IPageToDeleteWithMeta): void => { const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { if (typeof pathOrPathsToDelete !== 'string') { return; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx index 9ee464391f3..766f5648642 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx @@ -3,7 +3,6 @@ import React, { useCallback, useState } from 'react'; import nodePath from 'path'; - import { DevidedPagePath, pathUtils } from '@growi/core'; import { useTranslation } from 'react-i18next'; import { UncontrolledTooltip, DropdownToggle } from 'reactstrap'; @@ -12,8 +11,6 @@ import { unbookmark } from '~/client/services/page-operation'; import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Put } from '~/client/util/apiv3-client'; import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; -import { OnDeletedFunction } from '~/interfaces/ui'; -import { usePageDeleteModal } from '~/stores/modal'; import ClosableTextInput, { AlertInfo, AlertType } from '../../Common/ClosableTextInput'; @@ -21,23 +18,26 @@ import { MenuItemType, PageItemControl } from '../../Common/Dropdown/PageItemCon type Props = { - page: IPageHasId, - refreshBookmarkList: () => void + bookmarkedPage: IPageHasId, + onUnbookmarked: () => void, + onRenamed: () => void, + onDeleted: (pageToDelete: IPageToDeleteWithMeta) => void } const BookmarkItem = (props: Props): JSX.Element => { const { t } = useTranslation(); - const { page, refreshBookmarkList } = props; + const { + bookmarkedPage, onUnbookmarked, onRenamed, onDeleted, + } = props; const [isRenameInputShown, setRenameInputShown] = useState(false); - const dPagePath = new DevidedPagePath(page.path, false, true); + const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true); const { latter: pageTitle, former: formerPagePath } = dPagePath; - const bookmarkItemId = `bookmark-item-${page._id}`; - const { open: openDeleteModal } = usePageDeleteModal(); + const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`; const bookmarkMenuItemClickHandler = useCallback(async() => { - await unbookmark(page._id); - refreshBookmarkList(); - }, [page, refreshBookmarkList]); + await unbookmark(bookmarkedPage._id); + onUnbookmarked(); + }, [onUnbookmarked, bookmarkedPage]); const renameMenuItemClickHandler = useCallback(() => { setRenameInputShown(true); @@ -54,10 +54,10 @@ const BookmarkItem = (props: Props): JSX.Element => { return null; }; - const pressEnterForRenameHandler = (async(inputText: string) => { - const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); + const pressEnterForRenameHandler = useCallback(async(inputText: string) => { + const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(bookmarkedPage.path ?? '')); const newPagePath = nodePath.resolve(parentPath, inputText); - if (newPagePath === page.path) { + if (newPagePath === bookmarkedPage.path) { setRenameInputShown(false); return; } @@ -65,94 +65,75 @@ const BookmarkItem = (props: Props): JSX.Element => { try { setRenameInputShown(false); await apiv3Put('/pages/rename', { - pageId: page._id, - revisionId: page.revision, + pageId: bookmarkedPage._id, + revisionId: bookmarkedPage.revision, newPagePath, }); - refreshBookmarkList(); - toastSuccess(t('renamed_pages', { path: page.path })); + onRenamed(); + toastSuccess(t('renamed_pages', { path: bookmarkedPage.path })); } catch (err) { setRenameInputShown(true); toastError(err); } - }); + }, [bookmarkedPage, onRenamed, t]); const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { - const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => { - const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { - if (typeof pathOrPathsToDelete !== 'string') { - return; - } - const path = pathOrPathsToDelete; - - if (isCompletely) { - toastSuccess(t('deleted_pages_completely', { path })); - } - else { - toastSuccess(t('deleted_pages', { path })); - } - refreshBookmarkList(); - }; - openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); - }; - - if (page._id == null || page.path == null) { + if (bookmarkedPage._id == null || bookmarkedPage.path == null) { throw Error('_id and path must not be null.'); } const pageToDelete: IPageToDeleteWithMeta = { data: { - _id: page._id, - revision: page.revision as string, - path: page.path, + _id: bookmarkedPage._id, + revision: bookmarkedPage.revision as string, + path: bookmarkedPage.path, }, meta: pageInfo, }; - onClickDeleteMenuItem(pageToDelete); - }, [page, openDeleteModal, refreshBookmarkList, t]); + onDeleted(pageToDelete); + }, [bookmarkedPage, onDeleted]); return ( - <> -
      -
    • - { isRenameInputShown ? ( - { setRenameInputShown(false) }} - onPressEnter={pressEnterForRenameHandler} - inputValidator={inputValidator} - /> - ) : ( - -

      {pageTitle}

      -
      - )} - - - - - - - { formerPagePath || '/' } - -
    • -
      - +
      +
    • + { isRenameInputShown ? ( + { setRenameInputShown(false) }} + onPressEnter={pressEnterForRenameHandler} + inputValidator={inputValidator} + /> + ) : ( + +

      {pageTitle}

      +
      + )} + + + + + + + { formerPagePath } + +
    • +
      ); }; From dbe78fcee54c8d1a8f8446de86fa8bb816423dcb Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 5 Aug 2022 11:51:52 +0800 Subject: [PATCH 042/826] Improve code https://youtrack.weseek.co.jp/issue/GW-7841 - Rename method onBookmarkItemDeleted to deleteBookmarkItem - Update deleteBookmarkItem return type --- packages/app/src/components/Sidebar/Bookmarks.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 79632a1b3e4..ffd06aee1b3 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -148,7 +148,7 @@ const Bookmarks = () : JSX.Element => { const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); const { open: openDeleteModal } = usePageDeleteModal(); - const onBookmarkItemDeleted = (pageToDelete: IPageToDeleteWithMeta):void => { + const deleteBookmarkItem = (pageToDelete: IPageToDeleteWithMeta) => { const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { if (typeof pathOrPathsToDelete !== 'string') { return; @@ -184,7 +184,7 @@ const Bookmarks = () : JSX.Element => { bookmarkedPage={currentUserBookmark} onUnbookmarked={mutateCurrentUserBookmarks} onRenamed={mutateCurrentUserBookmarks} - onDeleted={onBookmarkItemDeleted} + onDeleted={deleteBookmarkItem} /> ); })} From f490320e66d475bd42c4443b4fb0dc63db0191b4 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 8 Aug 2022 14:56:49 +0800 Subject: [PATCH 043/826] Rename method and props https://youtrack.weseek.co.jp/issue/GW-7841 - Rename props onDeleted to onClickDeleteMenuItem - Rename deleteBookmarkItem method to deleteMenuItemClickHandler - Rename onDeletedHandler method to pageDeletedHandler --- .../app/src/components/Sidebar/Bookmarks.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index ffd06aee1b3..590338507ca 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -25,12 +25,12 @@ type Props = { bookmarkedPage: IPageHasId, onUnbookmarked: () => void, onRenamed: () => void, - onDeleted: (pageToDelete: IPageToDeleteWithMeta) => void + onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void } const BookmarkItem = (props: Props) => { const { - bookmarkedPage, onUnbookmarked, onRenamed, onDeleted, + bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, } = props; const { t } = useTranslation(); const [isRenameInputShown, setRenameInputShown] = useState(false); @@ -97,8 +97,8 @@ const BookmarkItem = (props: Props) => { meta: pageInfo, }; - onDeleted(pageToDelete); - }, [bookmarkedPage, onDeleted]); + onClickDeleteMenuItem(pageToDelete); + }, [bookmarkedPage, onClickDeleteMenuItem]); return (
      @@ -148,8 +148,8 @@ const Bookmarks = () : JSX.Element => { const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); const { open: openDeleteModal } = usePageDeleteModal(); - const deleteBookmarkItem = (pageToDelete: IPageToDeleteWithMeta) => { - const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { + const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { if (typeof pathOrPathsToDelete !== 'string') { return; } @@ -163,7 +163,7 @@ const Bookmarks = () : JSX.Element => { } mutateCurrentUserBookmarks(); }; - openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler }); + openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); }; const renderBookmarkList = () => { @@ -184,7 +184,7 @@ const Bookmarks = () : JSX.Element => { bookmarkedPage={currentUserBookmark} onUnbookmarked={mutateCurrentUserBookmarks} onRenamed={mutateCurrentUserBookmarks} - onDeleted={deleteBookmarkItem} + onClickDeleteMenuItem={deleteMenuItemClickHandler} /> ); })} From 64ecd420012e9ab81b8a1b76d7adca4b405e9045 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 8 Aug 2022 15:02:55 +0800 Subject: [PATCH 044/826] Update BookmarkItem.tsx --- .../app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx index 766f5648642..b070ee9b4b6 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx @@ -21,13 +21,13 @@ type Props = { bookmarkedPage: IPageHasId, onUnbookmarked: () => void, onRenamed: () => void, - onDeleted: (pageToDelete: IPageToDeleteWithMeta) => void + onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void } const BookmarkItem = (props: Props): JSX.Element => { const { t } = useTranslation(); const { - bookmarkedPage, onUnbookmarked, onRenamed, onDeleted, + bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, } = props; const [isRenameInputShown, setRenameInputShown] = useState(false); const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true); @@ -92,8 +92,8 @@ const BookmarkItem = (props: Props): JSX.Element => { meta: pageInfo, }; - onDeleted(pageToDelete); - }, [bookmarkedPage, onDeleted]); + onClickDeleteMenuItem(pageToDelete); + }, [bookmarkedPage, onClickDeleteMenuItem]); return (
      From 745259df3c6d2faedc402d42efa6a1e4706117b6 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 13 Oct 2022 18:48:57 +0800 Subject: [PATCH 045/826] Create BookmarkFolder Model https://youtrack.weseek.co.jp/issue/GW-7833 - Create BookmarkFolder Model - Add createBookmarkFolder method --- .../app/src/server/models/bookmark-folder.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 packages/app/src/server/models/bookmark-folder.ts diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts new file mode 100644 index 00000000000..e526da8982f --- /dev/null +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -0,0 +1,31 @@ +import { Ref, IUser, IUserHasId } from '@growi/core'; + +import { Model, Schema, Document } from 'mongoose'; +import { getOrCreateModel } from '../util/mongoose-utils'; + +export interface BookmarkFolderDocument extends Document { + name : string, + owner: Ref, + parent?: BookmarkFolderDocument +} + +export type BookmarkFolderModel = Model + +const bookmarkFolderSchema = new Schema({ + name: {type: String}, + owner: { type: Schema.Types.ObjectId, ref: 'User'}, + parent: { type: Schema.Types.ObjectId, ref: 'BookmarkFolder', required: false} +}); + +bookmarkFolderSchema.statics.createBookmarkFolder = async function (name: string, user:IUserHasId, parent?: BookmarkFolderDocument ): Promise { + const BookmarkFolder = this; + const bookmarkFolder = new BookmarkFolder(); + bookmarkFolder.name = name; + bookmarkFolder.owner = user._id; + if(parent != null ){ + bookmarkFolder.parent = parent._id; + } + return bookmarkFolder.save() +} + +export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema) From 4e8ee1a2dceabeace4b05f95d175ea42ba4f88e2 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 14 Oct 2022 18:56:22 +0800 Subject: [PATCH 046/826] Create api for create bookmark folder https://youtrack.weseek.co.jp/issue/GW-7833 - Update BookmarkFolderDocument - Add new type of IBookmarkFolderDocument - Update BookmarkFolderModel - Update createByParameters statics function - Create route new route file bookmark-folder - Create store bookmark-folder method in bookmark-folder route - Import bookmark-folder route to apiV3 route --- .../app/src/server/models/bookmark-folder.ts | 50 +++++++++++-------- .../server/routes/apiv3/bookmark-folder.ts | 45 +++++++++++++++++ packages/app/src/server/routes/apiv3/index.js | 1 + 3 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 packages/app/src/server/routes/apiv3/bookmark-folder.ts diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index e526da8982f..16bcae8580c 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -1,31 +1,41 @@ -import { Ref, IUser, IUserHasId } from '@growi/core'; +import { Ref, IUser } from '@growi/core'; +import { + Types, Document, Model, Schema, +} from 'mongoose'; -import { Model, Schema, Document } from 'mongoose'; + +import loggerFactory from '../../utils/logger'; import { getOrCreateModel } from '../util/mongoose-utils'; +const logger = loggerFactory('growi:models:bookmark-folder'); + +export type IBookmarkFolderDocument = { + name: string + owner: Ref + parent?: Ref +} export interface BookmarkFolderDocument extends Document { - name : string, - owner: Ref, - parent?: BookmarkFolderDocument + _id: Types.ObjectId + name: string + owner: Types.ObjectId + parent?: IBookmarkFolderDocument } -export type BookmarkFolderModel = Model +export interface BookmarkFolderModel extends Model{ + createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument +} const bookmarkFolderSchema = new Schema({ - name: {type: String}, - owner: { type: Schema.Types.ObjectId, ref: 'User'}, - parent: { type: Schema.Types.ObjectId, ref: 'BookmarkFolder', required: false} + name: { type: String }, + owner: { type: Schema.Types.ObjectId, ref: 'User', index: true }, + parent: { type: Schema.Types.ObjectId, refPath: 'BookmarkFolder', required: false }, }); -bookmarkFolderSchema.statics.createBookmarkFolder = async function (name: string, user:IUserHasId, parent?: BookmarkFolderDocument ): Promise { - const BookmarkFolder = this; - const bookmarkFolder = new BookmarkFolder(); - bookmarkFolder.name = name; - bookmarkFolder.owner = user._id; - if(parent != null ){ - bookmarkFolder.parent = parent._id; - } - return bookmarkFolder.save() -} -export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema) +bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolderDocument): Promise { + const bookmarkFolder = await this.create(params) as unknown as IBookmarkFolderDocument; + return bookmarkFolder; +}; + + +export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema); diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts new file mode 100644 index 00000000000..1ea77bdc1fc --- /dev/null +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -0,0 +1,45 @@ +import express, { Request } from 'express'; +import { body } from 'express-validator'; + +import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; +import loggerFactory from '~/utils/logger'; + +import Crowi from '../../crowi'; +import BookmarkFolder from '../../models/bookmark-folder'; + +import { ApiV3Response } from './interfaces/apiv3-response'; + +const logger = loggerFactory('growi:routes:apiv3:bookmark-folder'); + +const validator = { + bookmarkFolder: [ + body('name').isString().withMessage('name must be a string'), + body('owner').isMongoId().withMessage('owner must be a mongo ID'), + body('parent').optional().isMongoId().withMessage('parent must be a mongo ID'), + ], +}; + +module.exports = (crowi: Crowi) => { + const loginRequiredStrictly = require('../../middlewares/login-required')(crowi); + const accessTokenParser = require('../../middlewares/access-token-parser')(crowi); + const router = express.Router(); + router.post('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, apiV3FormValidator, async(req: Request, res: ApiV3Response) => { + const data = { + name: req.body.name, + owner: req.body.owner, + parent: req.body.parent, + }; + + try { + const bookmarkFolder = await BookmarkFolder.createByParameters(data); + logger.debug('bookmark folder created', bookmarkFolder); + return res.apiv3({ bookmarkFolder }); + } + catch (err) { + logger.error('create bookmark folder failed', err); + return res.apiv3Err(err, 500); + } + }); + + return router; +}; diff --git a/packages/app/src/server/routes/apiv3/index.js b/packages/app/src/server/routes/apiv3/index.js index 3818460f35e..62d6730d96a 100644 --- a/packages/app/src/server/routes/apiv3/index.js +++ b/packages/app/src/server/routes/apiv3/index.js @@ -101,6 +101,7 @@ module.exports = (crowi, app, isInstalled) => { router.use('/user-ui-settings', require('./user-ui-settings')(crowi)); + router.use('/bookmark-folder', require('./bookmark-folder')(crowi)); return [router, routerForAdmin, routerForAuth]; }; From d6f9a592bfbedc9939f8c4bd6d168aeefb35c068 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 17 Oct 2022 18:37:17 +0800 Subject: [PATCH 047/826] Create api list and delete bookmark folder https://youtrack.weseek.co.jp/issue/GW-7833 - Add method to find all bookmark folder by user id - Add method to delete bookmark folder and children - Fix express validator rule for bookmark folder - Update module import and requirement - Update store bookmark folder route - Add list and delete route --- .../app/src/server/models/bookmark-folder.ts | 12 +++++ .../server/routes/apiv3/bookmark-folder.ts | 47 ++++++++++++++----- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 16bcae8580c..a5c528acfcc 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -23,6 +23,8 @@ export interface BookmarkFolderDocument extends Document { export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument + findByUser(user: string): IBookmarkFolderDocument[] + deleteFolderAndChildren(bookmarkFolderId: string): void } const bookmarkFolderSchema = new Schema({ @@ -37,5 +39,15 @@ bookmarkFolderSchema.statics.createByParameters = async function(params: IBookma return bookmarkFolder; }; +bookmarkFolderSchema.statics.findByUser = async function(userId: string): Promise { + // TODO: Get all folder structure + return this.find({ owner: userId }); +}; + +bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(boookmarkFolderId: string): Promise { + // Delete parent and all children folder + const bookmarkFolder = await this.findByIdAndDelete(boookmarkFolderId); + await this.deleteMany({ parent: bookmarkFolder?.id }); +}; export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema); diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index 1ea77bdc1fc..522441f67fe 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -1,33 +1,35 @@ -import express, { Request } from 'express'; import { body } from 'express-validator'; import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; import loggerFactory from '~/utils/logger'; -import Crowi from '../../crowi'; import BookmarkFolder from '../../models/bookmark-folder'; -import { ApiV3Response } from './interfaces/apiv3-response'; - const logger = loggerFactory('growi:routes:apiv3:bookmark-folder'); +const express = require('express'); + +const router = express.Router(); + const validator = { bookmarkFolder: [ body('name').isString().withMessage('name must be a string'), - body('owner').isMongoId().withMessage('owner must be a mongo ID'), - body('parent').optional().isMongoId().withMessage('parent must be a mongo ID'), + body('parent').optional({ nullable: true }), ], }; -module.exports = (crowi: Crowi) => { - const loginRequiredStrictly = require('../../middlewares/login-required')(crowi); +module.exports = (crowi) => { const accessTokenParser = require('../../middlewares/access-token-parser')(crowi); - const router = express.Router(); - router.post('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, apiV3FormValidator, async(req: Request, res: ApiV3Response) => { + const loginRequiredStrictly = require('../../middlewares/login-required')(crowi); + + // Create new bookmark folder + router.post('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, apiV3FormValidator, async(req, res) => { + const owner = req.user?._id; + const { name, parent } = req.body; const data = { - name: req.body.name, - owner: req.body.owner, - parent: req.body.parent, + name, + owner, + parent, }; try { @@ -41,5 +43,24 @@ module.exports = (crowi: Crowi) => { } }); + // List all bookmark folders + router.get('/list', accessTokenParser, loginRequiredStrictly, async(req, res) => { + const bookmarkFolders = await BookmarkFolder.findByUser(req.user?._id); + return res.apiv3({ bookmarkFolders }); + }); + + + // Delete bookmark folder and children + router.delete('/', accessTokenParser, loginRequiredStrictly, async(req, res) => { + const { boookmarkFolderId } = req.body; + try { + await BookmarkFolder.deleteFolderAndChildren(boookmarkFolderId); + return res.apiv3(); + } + catch (err) { + logger.error(err); + return res.apiv3Err(err, 500); + } + }); return router; }; From a05c3774002ac582b595976586add61753ae026c Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 18 Oct 2022 18:22:35 +0800 Subject: [PATCH 048/826] Add list child folder api https://youtrack.weseek.co.jp/issue/GW-7833 - Update create bookmark folder method - Update and rename method fid folder by user id - Add method to find child folder - Update list bookmark folder route - Add list-child route --- .../app/src/server/models/bookmark-folder.ts | 21 ++++++++++++------- .../server/routes/apiv3/bookmark-folder.ts | 12 ++++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index a5c528acfcc..3f5c1bf2966 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -18,12 +18,13 @@ export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId name: string owner: Types.ObjectId - parent?: IBookmarkFolderDocument + parent?: BookmarkFolderDocument } export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument - findByUser(user: string): IBookmarkFolderDocument[] + findParentFolderByUserId(user: Types.ObjectId | string): IBookmarkFolderDocument[] + findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): void } @@ -34,14 +35,20 @@ const bookmarkFolderSchema = new Schema { - const bookmarkFolder = await this.create(params) as unknown as IBookmarkFolderDocument; +bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolderDocument): Promise { + const bookmarkFolder = await this.create(params) as unknown as BookmarkFolderDocument; return bookmarkFolder; }; -bookmarkFolderSchema.statics.findByUser = async function(userId: string): Promise { - // TODO: Get all folder structure - return this.find({ owner: userId }); +bookmarkFolderSchema.statics.findParentFolderByUserId = async function(userId: Types.ObjectId | string): Promise { + const bookmarks = this.find({ owner: userId }, { parent: null }) as unknown as BookmarkFolderDocument[]; + return bookmarks; +}; + +bookmarkFolderSchema.statics.findChildFolderById = async function(parentFolderId: Types.ObjectId | string): Promise { + const parentFolder = this.findById(parentFolderId) as unknown as BookmarkFolderDocument; + const childFolders = this.find({ parent: parentFolder.id }); + return childFolders; }; bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(boookmarkFolderId: string): Promise { diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index 522441f67fe..bebe8a1b16f 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -1,4 +1,4 @@ -import { body } from 'express-validator'; +import { body, param } from 'express-validator'; import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; import loggerFactory from '~/utils/logger'; @@ -13,6 +13,7 @@ const router = express.Router(); const validator = { bookmarkFolder: [ + param('parentId').isString().withMessage('parentId is required'), body('name').isString().withMessage('name must be a string'), body('parent').optional({ nullable: true }), ], @@ -43,12 +44,17 @@ module.exports = (crowi) => { } }); - // List all bookmark folders + // List all main bookmark folders router.get('/list', accessTokenParser, loginRequiredStrictly, async(req, res) => { - const bookmarkFolders = await BookmarkFolder.findByUser(req.user?._id); + const bookmarkFolders = await BookmarkFolder.findParentFolderByUserId(req.user?._id); return res.apiv3({ bookmarkFolders }); }); + router.post('/list-child/', accessTokenParser, loginRequiredStrictly, async(req, res) => { + const { parentId } = req.body; + const bookmarkFolders = await BookmarkFolder.findChildFolderById(parentId); + return res.apiv3({ bookmarkFolders }); + }); // Delete bookmark folder and children router.delete('/', accessTokenParser, loginRequiredStrictly, async(req, res) => { From 93a5a7a60347bb395daedc0a38260d9297aa90cd Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 19 Oct 2022 19:04:11 +0800 Subject: [PATCH 049/826] Add update bookmark folder api https://youtrack.weseek.co.jp/issue/GW-7833 - Update parent type - Add updateBookmarkFolder statics in BookmarkFolder schema - Update create bookmark folder method - Update findChildFolderById method - Update create bookmark params - Change get children list route to get - Add update bookmark folder route - Add try catch block to all bookmark folder route --- .../app/src/server/models/bookmark-folder.ts | 28 ++++++++++--- .../server/routes/apiv3/bookmark-folder.ts | 42 +++++++++++++------ 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 3f5c1bf2966..83ac51a5cb9 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -12,13 +12,13 @@ const logger = loggerFactory('growi:models:bookmark-folder'); export type IBookmarkFolderDocument = { name: string owner: Ref - parent?: Ref + parent?: string } export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId name: string owner: Types.ObjectId - parent?: BookmarkFolderDocument + parent?: Types.ObjectId | null } export interface BookmarkFolderModel extends Model{ @@ -26,6 +26,7 @@ export interface BookmarkFolderModel extends Model{ findParentFolderByUserId(user: Types.ObjectId | string): IBookmarkFolderDocument[] findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): void + updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null } const bookmarkFolderSchema = new Schema({ @@ -36,18 +37,21 @@ const bookmarkFolderSchema = new Schema { - const bookmarkFolder = await this.create(params) as unknown as BookmarkFolderDocument; + const { name, owner, parent } = params; + const parentFolder = await this.findById(parent); + + const bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id || null }) as unknown as BookmarkFolderDocument; return bookmarkFolder; }; bookmarkFolderSchema.statics.findParentFolderByUserId = async function(userId: Types.ObjectId | string): Promise { - const bookmarks = this.find({ owner: userId }, { parent: null }) as unknown as BookmarkFolderDocument[]; + const bookmarks = this.find({ owner: userId, parent: null }) as unknown as BookmarkFolderDocument[]; return bookmarks; }; bookmarkFolderSchema.statics.findChildFolderById = async function(parentFolderId: Types.ObjectId | string): Promise { - const parentFolder = this.findById(parentFolderId) as unknown as BookmarkFolderDocument; - const childFolders = this.find({ parent: parentFolder.id }); + const parentFolder = await this.findById(parentFolderId) as unknown as BookmarkFolderDocument; + const childFolders = await this.find({ parent: parentFolder._id }); return childFolders; }; @@ -57,4 +61,16 @@ bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(boookmarkF await this.deleteMany({ parent: bookmarkFolder?.id }); }; +bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolderId: string, name: string, parent: string): + Promise { + + const parentFolder = await this.findById(parent); + const updateFields = { + name, parent: parentFolder?._id || null, + }; + const bookmarkFolder = await this.findByIdAndUpdate(bookmarkFolderId, { $set: updateFields }, { new: true }); + return bookmarkFolder; + +}; + export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema); diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index bebe8a1b16f..6a346e3fc9b 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -13,7 +13,6 @@ const router = express.Router(); const validator = { bookmarkFolder: [ - param('parentId').isString().withMessage('parentId is required'), body('name').isString().withMessage('name must be a string'), body('parent').optional({ nullable: true }), ], @@ -27,14 +26,12 @@ module.exports = (crowi) => { router.post('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, apiV3FormValidator, async(req, res) => { const owner = req.user?._id; const { name, parent } = req.body; - const data = { - name, - owner, - parent, + const params = { + name, owner, parent, }; try { - const bookmarkFolder = await BookmarkFolder.createByParameters(data); + const bookmarkFolder = BookmarkFolder.createByParameters(params); logger.debug('bookmark folder created', bookmarkFolder); return res.apiv3({ bookmarkFolder }); } @@ -46,14 +43,24 @@ module.exports = (crowi) => { // List all main bookmark folders router.get('/list', accessTokenParser, loginRequiredStrictly, async(req, res) => { - const bookmarkFolders = await BookmarkFolder.findParentFolderByUserId(req.user?._id); - return res.apiv3({ bookmarkFolders }); + try { + const bookmarkFolders = await BookmarkFolder.findParentFolderByUserId(req.user?._id); + return res.apiv3({ bookmarkFolders }); + } + catch (err) { + return res.apiv3Err(err, 500); + } }); - router.post('/list-child/', accessTokenParser, loginRequiredStrictly, async(req, res) => { - const { parentId } = req.body; - const bookmarkFolders = await BookmarkFolder.findChildFolderById(parentId); - return res.apiv3({ bookmarkFolders }); + router.get('/list-child/:parentId', accessTokenParser, loginRequiredStrictly, async(req, res) => { + const { parentId } = req.params; + try { + const bookmarkFolders = await BookmarkFolder.findChildFolderById(parentId); + return res.apiv3({ bookmarkFolders }); + } + catch (err) { + return res.apiv3Err(err, 500); + } }); // Delete bookmark folder and children @@ -68,5 +75,16 @@ module.exports = (crowi) => { return res.apiv3Err(err, 500); } }); + + router.put('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, async(req, res) => { + const { bookmarkFolderId, name, parent } = req.body; + try { + const bookmarkFolder = await BookmarkFolder.updateBookmarkFolder(bookmarkFolderId, name, parent); + return res.apiv3({ bookmarkFolder }); + } + catch (err) { + return res.apiv3Err(err, 500); + } + }); return router; }; From cb4f46626b92bd9332679bb290d5bbac98bce28a Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 20 Oct 2022 18:23:51 +0800 Subject: [PATCH 050/826] Add bookmark folder button to Sidebar https://youtrack.weseek.co.jp/issue/GW-7895 - Create bookmark folder component - Implement ClosableTextInput to BookmarkFolder Component - Create SWR to fetch users bookmark folder - Store bookmark folder from Bookmark component --- .../app/src/components/Sidebar/Bookmarks.tsx | 46 ++++++++++++++++-- .../Sidebar/Bookmarks/BookmarkFolder.tsx | 48 +++++++++++++++++++ packages/app/src/stores/bookmark.ts | 10 ++++ 3 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 47e8562e537..70025965aec 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,25 +1,31 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { toastSuccess } from '~/client/util/apiNotification'; +import { toastSuccess, toastError } from '~/client/util/apiNotification'; +import { apiv3Post } from '~/client/util/apiv3-client'; import { IPageToDeleteWithMeta } from '~/interfaces/page'; import { OnDeletedFunction } from '~/interfaces/ui'; -import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; +import { useSWRxCurrentUserBookmarkFolders, useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useIsGuestUser } from '~/stores/context'; import { usePageDeleteModal } from '~/stores/modal'; +import BookmarkFolder from './Bookmarks/BookmarkFolder'; import BookmarkItem from './Bookmarks/BookmarkItem'; - const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); + const { data: currentUserBookmarkFolder, mutate: mutateCurrentUserBookmarkFolder } = useSWRxCurrentUserBookmarkFolders(); const { open: openDeleteModal } = usePageDeleteModal(); + const [isRenameFolderShown, setIsRenameFolderShown] = useState(false); + const [folderName, setFolderName] = useState(''); + const [currentParentFolder, setCurrentParentFolder] = useState(null); + const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { if (typeof pathOrPathsToDelete !== 'string') { @@ -38,6 +44,25 @@ const Bookmarks = () : JSX.Element => { openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); }; + const onPressEnterHandler = async(folderName: string) => { + setFolderName(folderName); + try { + await apiv3Post('/bookmark-folder', { name: folderName, parent: currentParentFolder }); + setIsRenameFolderShown(false); + setFolderName(''); + mutateCurrentUserBookmarkFolder(); + toastSuccess(t('Create New Bookmark Folder Success')); + } + catch (err) { + toastError(err); + } + }; + + const onClickOutsideHandler = () => { + setIsRenameFolderShown(false); + setFolderName(''); + }; + const renderBookmarkList = () => { if (currentUserBookmarksData?.length === 0) { return ( @@ -70,6 +95,19 @@ const Bookmarks = () : JSX.Element => {

      {t('Bookmarks')}

      + {!isGuestUser && ( + <> + setIsRenameFolderShown(true)} + isRenameInputShown={isRenameFolderShown} + onClickOutside={onClickOutsideHandler} + onPressEnter={onPressEnterHandler} + folderName={folderName} + /> + {/* TODO: List Bookmark Folder */} + + ) + } { isGuestUser ? (

      diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx new file mode 100644 index 00000000000..63b70929b06 --- /dev/null +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx @@ -0,0 +1,48 @@ + +import React from 'react'; + +import { useTranslation } from 'next-i18next'; + +import ClosableTextInput from '~/components/Common/ClosableTextInput'; + + +type Props = { + onClickNewFolder: () => void + isRenameInputShown: boolean + folderName: string + onClickOutside: () => void + onPressEnter: (folderName: string) => void +} +const BookmarkFolder = (props: Props): JSX.Element => { + const { + onClickNewFolder, isRenameInputShown, folderName, onClickOutside, onPressEnter, + } = props; + const { t } = useTranslation(); + return ( + <> +
      + +
      + { + isRenameInputShown && ( +
      + +
      + ) + } + + ); +}; + +export default BookmarkFolder; diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index aa99ca0feae..b85bd97aad2 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -3,6 +3,7 @@ import useSWRImmutable from 'swr/immutable'; import { Nullable } from '~/interfaces/common'; import { IPageHasId } from '~/interfaces/page'; +import { IBookmarkFolderDocument } from '~/server/models/bookmark-folder'; import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -37,3 +38,12 @@ export const useSWRxCurrentUserBookmarks = (pageNum?: Nullable): SWRResp }), ); }; + +export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse => { + return useSWRImmutable( + '/bookmark-folder/list', + endpoint => apiv3Get(endpoint).then((response) => { + return response.data.bookmarkFolders; + }), + ); +}; From 7fcae70716bd5cbb22e03468817fb3abda6fc934 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 21 Oct 2022 19:20:03 +0800 Subject: [PATCH 051/826] Fix eslint error and throw an error if parent folder id is invalid https://youtrack.weseek.co.jp/issue/GW-7833 - Fix unresolved import of Nullable module - Fix get user id from currentUser - Create custom error class of InvalidParentBookmarkFolder - Update parent type in IBookmarkFolderDocument - Throwing error when parentFolder object is null --- .../app/src/server/models/bookmark-folder.ts | 30 +++++++++++++++---- packages/app/src/stores/bookmark.ts | 5 ++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 83ac51a5cb9..16eab0be846 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -1,4 +1,5 @@ import { Ref, IUser } from '@growi/core'; +import ExtensibleCustomError from 'extensible-custom-error'; import { Types, Document, Model, Schema, } from 'mongoose'; @@ -9,10 +10,13 @@ import { getOrCreateModel } from '../util/mongoose-utils'; const logger = loggerFactory('growi:models:bookmark-folder'); +export class InvalidParentBookmarkFolder extends ExtensibleCustomError {} + + export type IBookmarkFolderDocument = { name: string owner: Ref - parent?: string + parent?: Ref } export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId @@ -32,15 +36,31 @@ export interface BookmarkFolderModel extends Model{ const bookmarkFolderSchema = new Schema({ name: { type: String }, owner: { type: Schema.Types.ObjectId, ref: 'User', index: true }, - parent: { type: Schema.Types.ObjectId, refPath: 'BookmarkFolder', required: false }, + parent: { type: Schema.Types.ObjectId, ref: 'BookmarkFolder', required: false }, }); bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolderDocument): Promise { const { name, owner, parent } = params; - const parentFolder = await this.findById(parent); - - const bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id || null }) as unknown as BookmarkFolderDocument; + let bookmarkFolder; + try { + if (parent === null) { + bookmarkFolder = await this.create({ name, owner, parent: null }) as unknown as BookmarkFolderDocument; + } + else { + const parentFolder = await this.findById(parent); + if (!parentFolder) { + throw new InvalidParentBookmarkFolder("Parent folder doesn't exists"); + } + bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id }) as unknown as BookmarkFolderDocument; + } + } + catch (err) { + if (err instanceof InvalidParentBookmarkFolder) { + throw new InvalidParentBookmarkFolder(err); + } + return err; + } return bookmarkFolder; }; diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index aa99ca0feae..6e47c748021 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -1,7 +1,7 @@ +import { IUserHasId, Nullable } from '@growi/core'; import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; -import { Nullable } from '~/interfaces/common'; import { IPageHasId } from '~/interfaces/page'; import { apiv3Get } from '../client/util/apiv3-client'; @@ -25,8 +25,9 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon export const useSWRxCurrentUserBookmarks = (pageNum?: Nullable): SWRResponse => { const { data: currentUser } = useCurrentUser(); const currentPage = pageNum ?? 1; + const user = currentUser as IUserHasId; return useSWRImmutable( - currentUser != null ? `/bookmarks/${currentUser._id}` : null, + currentUser != null ? `/bookmarks/${user._id}` : null, endpoint => apiv3Get(endpoint, { page: currentPage }).then((response) => { const { paginationResult } = response.data; return paginationResult.docs.map((item) => { From a18d624fd5fad73082dc350cf16361fe3abed5b2 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 24 Oct 2022 15:57:55 +0800 Subject: [PATCH 052/826] Add error handler when parent folder doesn't exists https://youtrack.weseek.co.jp/issue/GW-7833 - Add error handler when parent folder doesn't exists - Update createByParameters method --- .../app/src/server/models/bookmark-folder.ts | 25 ++++++++----------- .../server/routes/apiv3/bookmark-folder.ts | 11 +++++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 16eab0be846..a8db58fa6fd 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -43,24 +43,19 @@ const bookmarkFolderSchema = new Schema { const { name, owner, parent } = params; let bookmarkFolder; - try { - if (parent === null) { - bookmarkFolder = await this.create({ name, owner, parent: null }) as unknown as BookmarkFolderDocument; - } - else { - const parentFolder = await this.findById(parent); - if (!parentFolder) { - throw new InvalidParentBookmarkFolder("Parent folder doesn't exists"); - } - bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id }) as unknown as BookmarkFolderDocument; - } + + if (parent === null) { + bookmarkFolder = await this.create({ name, owner, parent: null }) as unknown as BookmarkFolderDocument; } - catch (err) { - if (err instanceof InvalidParentBookmarkFolder) { - throw new InvalidParentBookmarkFolder(err); + else { + const parentFolder = await this.findById(parent); + if (!parentFolder) { + throw new InvalidParentBookmarkFolder("Parent folder doesn't exists"); } - return err; + bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id }) as unknown as BookmarkFolderDocument; } + + return bookmarkFolder; }; diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index 6a346e3fc9b..e24f3e6c5b5 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -1,9 +1,10 @@ -import { body, param } from 'express-validator'; +import { ErrorV3 } from '@growi/core'; +import { body } from 'express-validator'; import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; import loggerFactory from '~/utils/logger'; -import BookmarkFolder from '../../models/bookmark-folder'; +import BookmarkFolder, { InvalidParentBookmarkFolder } from '../../models/bookmark-folder'; const logger = loggerFactory('growi:routes:apiv3:bookmark-folder'); @@ -31,12 +32,14 @@ module.exports = (crowi) => { }; try { - const bookmarkFolder = BookmarkFolder.createByParameters(params); + const bookmarkFolder = await BookmarkFolder.createByParameters(params); logger.debug('bookmark folder created', bookmarkFolder); return res.apiv3({ bookmarkFolder }); } catch (err) { - logger.error('create bookmark folder failed', err); + if (err instanceof InvalidParentBookmarkFolder) { + return res.apiv3Err(new ErrorV3(err.message, 'failed_to_create_bookmark_folder')); + } return res.apiv3Err(err, 500); } }); From a814202a9918bac73583a4511f2cd623d53690da Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 24 Oct 2022 19:00:01 +0800 Subject: [PATCH 053/826] Get child bookmark folder count https://youtrack.weseek.co.jp/issue/GW-7895 - Update findParentFolderByUserId return type - Get child bookmark folder count - Update bookmark folder list SWR --- packages/app/src/components/Sidebar/Bookmarks.tsx | 7 +++++++ packages/app/src/server/models/bookmark-folder.ts | 2 +- packages/app/src/server/routes/apiv3/bookmark-folder.ts | 6 +++++- packages/app/src/stores/bookmark.ts | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 70025965aec..948bc533f03 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -105,6 +105,13 @@ const Bookmarks = () : JSX.Element => { folderName={folderName} /> {/* TODO: List Bookmark Folder */} +
        + {currentUserBookmarkFolder?.map((bookmarkFolder, idx) => { + return ( +
      • {bookmarkFolder.name}
      • + ); + })} +
      ) } diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index a8db58fa6fd..9ed3d6fe255 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -27,7 +27,7 @@ export interface BookmarkFolderDocument extends Document { export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument - findParentFolderByUserId(user: Types.ObjectId | string): IBookmarkFolderDocument[] + findParentFolderByUserId(user: Types.ObjectId | string): BookmarkFolderDocument[] findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): void updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index e24f3e6c5b5..eabd404bbf4 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -48,7 +48,11 @@ module.exports = (crowi) => { router.get('/list', accessTokenParser, loginRequiredStrictly, async(req, res) => { try { const bookmarkFolders = await BookmarkFolder.findParentFolderByUserId(req.user?._id); - return res.apiv3({ bookmarkFolders }); + const bookmarkFolderItems = await Promise.all(bookmarkFolders.map(async bookmarkFolder => ({ + bookmarkFolder, + childCount: await BookmarkFolder.countDocuments({ parent: bookmarkFolder._id }), + }))); + return res.apiv3({ bookmarkFolderItems }); } catch (err) { return res.apiv3Err(err, 500); diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 6c241d1f92a..693be560549 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -44,7 +44,7 @@ export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse apiv3Get(endpoint).then((response) => { - return response.data.bookmarkFolders; + return response.data.bookmarkFolderItems; }), ); }; From 7c19f7a31f70d17bb1f104a6c701b5faf72f97c6 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 25 Oct 2022 13:56:06 +0800 Subject: [PATCH 054/826] Check if parent folder id is valid and add deleted count https://youtrack.weseek.co.jp/issue/GW-7833 - Update parent type to string in IBookmarkFolderDocument type - Update return type of deleteFolderAndChildren method - Check if parent bookmark folder id is valid and parent folder exists - Add return value of deleteFolderAndChildren method - Fix delete route parameter and add return value --- .../app/src/server/models/bookmark-folder.ts | 22 +++++++++++++------ .../server/routes/apiv3/bookmark-folder.ts | 11 +++++----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index a8db58fa6fd..fd1012e9c25 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -1,4 +1,5 @@ import { Ref, IUser } from '@growi/core'; +import { isValidObjectId } from '@growi/core/src/utils/objectid-utils'; import ExtensibleCustomError from 'extensible-custom-error'; import { Types, Document, Model, Schema, @@ -16,7 +17,7 @@ export class InvalidParentBookmarkFolder extends ExtensibleCustomError {} export type IBookmarkFolderDocument = { name: string owner: Ref - parent?: Ref + parent?: string } export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId @@ -29,7 +30,7 @@ export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument findParentFolderByUserId(user: Types.ObjectId | string): IBookmarkFolderDocument[] findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise - deleteFolderAndChildren(bookmarkFolderId: string): void + deleteFolderAndChildren(bookmarkFolderId: string): {deletedCount: number} updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null } @@ -48,14 +49,16 @@ bookmarkFolderSchema.statics.createByParameters = async function(params: IBookma bookmarkFolder = await this.create({ name, owner, parent: null }) as unknown as BookmarkFolderDocument; } else { + // Check if parent folder id is valid and parent folder exists + const isParentFolderIdValid = isValidObjectId(parent); const parentFolder = await this.findById(parent); - if (!parentFolder) { - throw new InvalidParentBookmarkFolder("Parent folder doesn't exists"); + + if (!isParentFolderIdValid || parentFolder == null) { + throw new InvalidParentBookmarkFolder("Parent folder id is invalid or parent folder doesn't exists"); } bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id }) as unknown as BookmarkFolderDocument; } - return bookmarkFolder; }; @@ -70,10 +73,15 @@ bookmarkFolderSchema.statics.findChildFolderById = async function(parentFolderId return childFolders; }; -bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(boookmarkFolderId: string): Promise { +bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(boookmarkFolderId: Types.ObjectId | string): Promise<{deletedCount: number}> { // Delete parent and all children folder const bookmarkFolder = await this.findByIdAndDelete(boookmarkFolderId); - await this.deleteMany({ parent: bookmarkFolder?.id }); + let deletedCount = 0; + if (bookmarkFolder != null) { + const childFolders = await this.deleteMany({ parent: bookmarkFolder?.id }); + deletedCount = childFolders.deletedCount + 1; + } + return { deletedCount }; }; bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolderId: string, name: string, parent: string): diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index e24f3e6c5b5..b8c17697716 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -15,7 +15,7 @@ const router = express.Router(); const validator = { bookmarkFolder: [ body('name').isString().withMessage('name must be a string'), - body('parent').optional({ nullable: true }), + body('parent').isMongoId().optional({ nullable: true }), ], }; @@ -67,11 +67,12 @@ module.exports = (crowi) => { }); // Delete bookmark folder and children - router.delete('/', accessTokenParser, loginRequiredStrictly, async(req, res) => { - const { boookmarkFolderId } = req.body; + router.delete('/:id', accessTokenParser, loginRequiredStrictly, async(req, res) => { + const { id } = req.params; try { - await BookmarkFolder.deleteFolderAndChildren(boookmarkFolderId); - return res.apiv3(); + const result = await BookmarkFolder.deleteFolderAndChildren(id); + const { deletedCount } = result; + return res.apiv3({ deletedCount }); } catch (err) { logger.error(err); From 3bea49d9f212edd05398e6c3719865d52f92aeea Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 25 Oct 2022 18:23:17 +0800 Subject: [PATCH 055/826] Create bookmark folder list component https://youtrack.weseek.co.jp/issue/GW-7895 - Update current user bookmark folder return type - Add BookmarkFolderItems type - Create bookmark folder tree and item component - Create temporary style for bookmark item - Implement BookmarkFolderTree component on Bookmark sidebar --- .../app/src/components/Sidebar/Bookmarks.tsx | 9 +- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 49 ++++++ .../Bookmarks/BookmarkFolderTree.module.scss | 141 ++++++++++++++++++ .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 32 ++++ .../app/src/server/models/bookmark-folder.ts | 4 + packages/app/src/stores/bookmark.ts | 4 +- 6 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx create mode 100644 packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss create mode 100644 packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 948bc533f03..1cad1eb16eb 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -13,6 +13,7 @@ import { usePageDeleteModal } from '~/stores/modal'; import BookmarkFolder from './Bookmarks/BookmarkFolder'; +import BookmarkFolderTree from './Bookmarks/BookmarkFolderTree'; import BookmarkItem from './Bookmarks/BookmarkItem'; const Bookmarks = () : JSX.Element => { @@ -105,13 +106,7 @@ const Bookmarks = () : JSX.Element => { folderName={folderName} /> {/* TODO: List Bookmark Folder */} -
        - {currentUserBookmarkFolder?.map((bookmarkFolder, idx) => { - return ( -
      • {bookmarkFolder.name}
      • - ); - })} -
      + ) } diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx new file mode 100644 index 00000000000..0612d45047f --- /dev/null +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -0,0 +1,49 @@ +import { FC } from 'react'; + +import { useTranslation } from 'next-i18next'; + +import CountBadge from '~/components/Common/CountBadge'; +import TriangleIcon from '~/components/Icons/TriangleIcon'; +import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; + + +type BookmarkFolderItemProps = { + bookmarkFolders: BookmarkFolderItems +} +const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { + const { bookmarkFolders } = props; + const { t } = useTranslation(); + const hasChildren = bookmarkFolders.childCount > 0; + return ( +
      +
    • +
      + {hasChildren && ( + + )} + + { +
      +

      {bookmarkFolders.bookmarkFolder.name}

      +
      + } + {hasChildren && ( +
      + +
      + )} +
      +
    • +
      + ); +}; + +export default BookmarkFolderItem; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss new file mode 100644 index 00000000000..0c0d6228eb9 --- /dev/null +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss @@ -0,0 +1,141 @@ +@use '~/styles/variables' as var; +$grw-sidebar-content-header-height: 58px; +$grw-sidebar-content-footer-height: 50px; +$grw-pagetree-item-padding-left: 10px; + +.grw-pagetree { + :global { + min-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); + + .btn-page-item-control { + .icon-plus::before { + font-size: 18px; + } + } + + .list-group-item { + .grw-visible-on-hover { + display: none; + } + + &:hover { + .grw-visible-on-hover { + display: block; + } + + .grw-count-badge { + display: none; + } + } + + .grw-pagetree-triangle-btn { + background-color: transparent; + transition: all 0.2s ease-out; + transform: rotate(0deg); + + &.grw-pagetree-open { + transform: rotate(90deg); + } + } + + .grw-pagetree-title-anchor { + width: 100%; + overflow: hidden; + text-decoration: none; + } + + .grw-pagetree-count-wrapper { + display: inline-block; + + &:hover { + display: none; + } + } + } + + .grw-pagetree-item-container { + .grw-triangle-container { + min-width: 35px; + height: 40px; + } + } + } + &:global{ + // To realize a hierarchical structure, set multiplied padding-left to each pagetree-item + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: 0; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 2; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 3; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 4; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 5; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 6; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 7; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 8; + } + > .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 9; + } + .grw-pagetree-item-children { + > .grw-pagetree-item-container { + > .list-group-item { + padding-left: $grw-pagetree-item-padding-left * 10; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx new file mode 100644 index 00000000000..bc747bd8212 --- /dev/null +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -0,0 +1,32 @@ +import React, { useRef } from 'react'; + +import { useTranslation } from 'next-i18next'; + +import { useSWRxCurrentUserBookmarkFolders } from '~/stores/bookmark'; + +import BookmarkFolderItem from './BookmarkFolderItem'; + +import styles from './BookmarkFolderTree.module.scss'; + +const BookmarkFolderTree = (): JSX.Element => { + const { t } = useTranslation(); + const rootFolderRef = useRef(null); + const { data: bookmarkFolderData, mutate: mutateBookmarkFolderData } = useSWRxCurrentUserBookmarkFolders(); + + if (bookmarkFolderData != null) { + return ( + +
        + {bookmarkFolderData.map((item, index) => { + return ( + + ); + })} +
      + ); + } + return <>; + +}; + +export default BookmarkFolderTree; diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 62599bfb736..bb38c675031 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -13,6 +13,10 @@ const logger = loggerFactory('growi:models:bookmark-folder'); export class InvalidParentBookmarkFolder extends ExtensibleCustomError {} +export type BookmarkFolderItems = { + bookmarkFolder: IBookmarkFolderDocument + childCount: number +} export type IBookmarkFolderDocument = { name: string diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 693be560549..f72c9fca669 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -3,7 +3,7 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; import { IPageHasId } from '~/interfaces/page'; -import { IBookmarkFolderDocument } from '~/server/models/bookmark-folder'; +import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -40,7 +40,7 @@ export const useSWRxCurrentUserBookmarks = (pageNum?: Nullable): SWRResp ); }; -export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse => { +export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse => { return useSWRImmutable( '/bookmark-folder/list', endpoint => apiv3Get(endpoint).then((response) => { From 865b401ea8c4a1bfcc0b67103fda54a92d7ece56 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 26 Oct 2022 18:16:48 +0800 Subject: [PATCH 056/826] Create folder tree structure https://youtrack.weseek.co.jp/issue/GW-7895 - Update bookmarkFolder type in BookmarkFolderItems - Update findChildFolderById return type - Update list child route return value - Add SWR data fetching for children folder - Update button triangle icon to expand bookmark folder tree - Add folder icon in bookmark folder list - Render children bookmark folder - Update implementation of BookmarkFolderTree and BookmarkFolderItem component --- .../app/src/components/Sidebar/Bookmarks.tsx | 5 +- .../Sidebar/Bookmarks/BookmarkFolder.tsx | 3 + .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 73 ++++++++++++++----- .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 15 ++-- .../app/src/server/models/bookmark-folder.ts | 6 +- .../server/routes/apiv3/bookmark-folder.ts | 6 +- packages/app/src/stores/bookmark.ts | 9 +++ 7 files changed, 87 insertions(+), 30 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 1cad1eb16eb..6d5617012ee 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -13,7 +13,6 @@ import { usePageDeleteModal } from '~/stores/modal'; import BookmarkFolder from './Bookmarks/BookmarkFolder'; -import BookmarkFolderTree from './Bookmarks/BookmarkFolderTree'; import BookmarkItem from './Bookmarks/BookmarkItem'; const Bookmarks = () : JSX.Element => { @@ -25,7 +24,7 @@ const Bookmarks = () : JSX.Element => { const [isRenameFolderShown, setIsRenameFolderShown] = useState(false); const [folderName, setFolderName] = useState(''); - const [currentParentFolder, setCurrentParentFolder] = useState(null); + const [currentParentFolder, setCurrentParentFolder] = useState(null); const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { @@ -105,8 +104,6 @@ const Bookmarks = () : JSX.Element => { onPressEnter={onPressEnterHandler} folderName={folderName} /> - {/* TODO: List Bookmark Folder */} - ) } diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx index 63b70929b06..07d880b2f59 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'next-i18next'; import ClosableTextInput from '~/components/Common/ClosableTextInput'; +import BookmarkFolderTree from './BookmarkFolderTree'; type Props = { onClickNewFolder: () => void @@ -18,6 +19,7 @@ const BookmarkFolder = (props: Props): JSX.Element => { onClickNewFolder, isRenameInputShown, folderName, onClickOutside, onPressEnter, } = props; const { t } = useTranslation(); + return ( <>
      @@ -41,6 +43,7 @@ const BookmarkFolder = (props: Props): JSX.Element => {
      ) } + ); }; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 0612d45047f..909db0b6364 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -1,47 +1,86 @@ -import { FC } from 'react'; +import { + FC, useCallback, useEffect, useState, +} from 'react'; import { useTranslation } from 'next-i18next'; import CountBadge from '~/components/Common/CountBadge'; import TriangleIcon from '~/components/Icons/TriangleIcon'; import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; +import { useSWRxChildBookmarkFolders } from '~/stores/bookmark'; type BookmarkFolderItemProps = { bookmarkFolders: BookmarkFolderItems + isOpen?: boolean } const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { - const { bookmarkFolders } = props; + const { bookmarkFolders, isOpen: _isOpen = false } = props; const { t } = useTranslation(); const hasChildren = bookmarkFolders.childCount > 0; + const [currentParentFolder, setCurrentParentFolder] = useState(null); + const [isActive, setIsActive] = useState(false); + const [isOpen, setIsOpen] = useState(_isOpen); + const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxChildBookmarkFolders(isOpen, currentParentFolder); + + useEffect(() => { + setCurrentParentFolder(bookmarkFolders.bookmarkFolder._id); + }, [bookmarkFolders]); + + const onClickHandler = useCallback(async() => { + setCurrentParentFolder(bookmarkFolders.bookmarkFolder._id); + setIsOpen(!isOpen); + setIsActive(!isActive); + mutateChildBookmarkData(); + }, [bookmarkFolders, isOpen, isActive, mutateChildBookmarkData]); + return ( -
      -
    • +
      +
    • {hasChildren && ( )} - - { -
      -

      {bookmarkFolders.bookmarkFolder.name}

      -
      - } - {hasChildren && ( -
      - -
      - )}
      + { +
      + +
      + } + { +
      +

      {bookmarkFolders.bookmarkFolder.name}

      +
      + } + {hasChildren && ( +
      + +
      + )} +
    • + { + isOpen && hasChildren && childBookmarkFolderData?.map(children => ( +
      + +
      + )) + }
      ); }; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx index bc747bd8212..8f1eb955d88 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React from 'react'; import { useTranslation } from 'next-i18next'; @@ -8,18 +8,23 @@ import BookmarkFolderItem from './BookmarkFolderItem'; import styles from './BookmarkFolderTree.module.scss'; + const BookmarkFolderTree = (): JSX.Element => { const { t } = useTranslation(); - const rootFolderRef = useRef(null); const { data: bookmarkFolderData, mutate: mutateBookmarkFolderData } = useSWRxCurrentUserBookmarkFolders(); + if (bookmarkFolderData != null) { return ( -
        - {bookmarkFolderData.map((item, index) => { +
          + {bookmarkFolderData.map((item) => { return ( - + ); })}
        diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index bb38c675031..bbafe759586 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -1,4 +1,4 @@ -import { Ref, IUser } from '@growi/core'; +import { Ref, IUser, HasObjectId } from '@growi/core'; import { isValidObjectId } from '@growi/core/src/utils/objectid-utils'; import ExtensibleCustomError from 'extensible-custom-error'; import { @@ -14,7 +14,7 @@ const logger = loggerFactory('growi:models:bookmark-folder'); export class InvalidParentBookmarkFolder extends ExtensibleCustomError {} export type BookmarkFolderItems = { - bookmarkFolder: IBookmarkFolderDocument + bookmarkFolder: IBookmarkFolderDocument & HasObjectId childCount: number } @@ -33,7 +33,7 @@ export interface BookmarkFolderDocument extends Document { export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument findParentFolderByUserId(user: Types.ObjectId | string): BookmarkFolderDocument[] - findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise + findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): {deletedCount: number} updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null } diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index c9e7bad5e27..39949515115 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -63,7 +63,11 @@ module.exports = (crowi) => { const { parentId } = req.params; try { const bookmarkFolders = await BookmarkFolder.findChildFolderById(parentId); - return res.apiv3({ bookmarkFolders }); + const bookmarkFolderItems = await Promise.all(bookmarkFolders.map(async bookmarkFolder => ({ + bookmarkFolder, + childCount: await BookmarkFolder.countDocuments({ parent: bookmarkFolder._id }), + }))); + return res.apiv3({ bookmarkFolderItems }); } catch (err) { return res.apiv3Err(err, 500); diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index f72c9fca669..2318a38bcc0 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -48,3 +48,12 @@ export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse): SWRResponse => { + return useSWRImmutable( + isOpen && parentId != null ? `/bookmark-folder/list-child/${parentId}` : null, + endpoint => apiv3Get(endpoint).then((response) => { + return response.data.bookmarkFolderItems; + }), + ); +}; From e237fe959f346d23b2600b98e3ca35aeb1136d1c Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 27 Oct 2022 18:21:45 +0800 Subject: [PATCH 057/826] Improve data fetching and update style https://youtrack.weseek.co.jp/issue/GW-7895 - Create and adjust styling based on page tree style - Create and separate SWR data fetching to load bookmark folder and children - Update bookmark folder list route - Add parentId parameter in list route - Adjust parameter of findParentFolderByUserId method - Update BookmarkFolderItems type - Unify SWR data fetching to load initial data and child --- .../app/src/components/Sidebar/Bookmarks.tsx | 10 +-- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 38 +++++---- .../Bookmarks/BookmarkFolderTree.module.scss | 78 +++++++++---------- .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 14 +++- .../components/Theme/ThemeIsland.global.scss | 6 ++ .../app/src/server/models/bookmark-folder.ts | 11 ++- .../server/routes/apiv3/bookmark-folder.ts | 23 ++---- packages/app/src/stores/bookmark-folder.ts | 16 ++++ packages/app/src/stores/bookmark.ts | 19 ----- .../src/styles/theme/_apply-colors-dark.scss | 27 +++++++ .../src/styles/theme/_apply-colors-light.scss | 16 ++++ .../app/src/styles/theme/_apply-colors.scss | 9 +++ .../src/styles/theme/mixins/_list-group.scss | 6 +- 13 files changed, 166 insertions(+), 107 deletions(-) create mode 100644 packages/app/src/stores/bookmark-folder.ts diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 6d5617012ee..eb96fa2242e 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -7,7 +7,8 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification'; import { apiv3Post } from '~/client/util/apiv3-client'; import { IPageToDeleteWithMeta } from '~/interfaces/page'; import { OnDeletedFunction } from '~/interfaces/ui'; -import { useSWRxCurrentUserBookmarkFolders, useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; +import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import { useIsGuestUser } from '~/stores/context'; import { usePageDeleteModal } from '~/stores/modal'; @@ -19,12 +20,11 @@ const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); - const { data: currentUserBookmarkFolder, mutate: mutateCurrentUserBookmarkFolder } = useSWRxCurrentUserBookmarkFolders(); + const { mutate: mutateInitialBookmarkFolderData } = useSWRxBookamrkFolderAndChild(true); const { open: openDeleteModal } = usePageDeleteModal(); const [isRenameFolderShown, setIsRenameFolderShown] = useState(false); const [folderName, setFolderName] = useState(''); - const [currentParentFolder, setCurrentParentFolder] = useState(null); const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { @@ -47,10 +47,10 @@ const Bookmarks = () : JSX.Element => { const onPressEnterHandler = async(folderName: string) => { setFolderName(folderName); try { - await apiv3Post('/bookmark-folder', { name: folderName, parent: currentParentFolder }); + await apiv3Post('/bookmark-folder', { name: folderName, parent: null }); setIsRenameFolderShown(false); setFolderName(''); - mutateCurrentUserBookmarkFolder(); + mutateInitialBookmarkFolderData(); toastSuccess(t('Create New Bookmark Folder Success')); } catch (err) { diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 909db0b6364..3b68168c5ba 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -7,45 +7,50 @@ import { useTranslation } from 'next-i18next'; import CountBadge from '~/components/Common/CountBadge'; import TriangleIcon from '~/components/Icons/TriangleIcon'; import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; -import { useSWRxChildBookmarkFolders } from '~/stores/bookmark'; +import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; type BookmarkFolderItemProps = { bookmarkFolders: BookmarkFolderItems isOpen?: boolean + updateActiveElement?: (parentId: string | null) => void + isActive?: boolean } const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { - const { bookmarkFolders, isOpen: _isOpen = false } = props; + const { + bookmarkFolders, isOpen: _isOpen = false, updateActiveElement, isActive, + } = props; const { t } = useTranslation(); - const hasChildren = bookmarkFolders.childCount > 0; + const hasChildren = bookmarkFolders.children.length > 0; const [currentParentFolder, setCurrentParentFolder] = useState(null); - const [isActive, setIsActive] = useState(false); const [isOpen, setIsOpen] = useState(_isOpen); - const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxChildBookmarkFolders(isOpen, currentParentFolder); + const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(isOpen, currentParentFolder); useEffect(() => { setCurrentParentFolder(bookmarkFolders.bookmarkFolder._id); }, [bookmarkFolders]); - const onClickHandler = useCallback(async() => { + const loadChildFolder = useCallback(async() => { setCurrentParentFolder(bookmarkFolders.bookmarkFolder._id); setIsOpen(!isOpen); - setIsActive(!isActive); + updateActiveElement?.(!isOpen ? bookmarkFolders.bookmarkFolder._id : null); mutateChildBookmarkData(); - }, [bookmarkFolders, isOpen, isActive, mutateChildBookmarkData]); + }, [bookmarkFolders, isOpen, updateActiveElement, mutateChildBookmarkData]); + return ( -
      • {hasChildren && (
      • { isOpen && hasChildren && childBookmarkFolderData?.map(children => ( -
        +
        )) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss index 0c0d6228eb9..d0312096bdb 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss @@ -1,9 +1,9 @@ @use '~/styles/variables' as var; $grw-sidebar-content-header-height: 58px; $grw-sidebar-content-footer-height: 50px; -$grw-pagetree-item-padding-left: 10px; +$grw-foldertree-item-padding-left: 10px; -.grw-pagetree { +.grw-foldertree { :global { min-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); @@ -28,23 +28,23 @@ $grw-pagetree-item-padding-left: 10px; } } - .grw-pagetree-triangle-btn { + .grw-foldertree-triangle-btn { background-color: transparent; transition: all 0.2s ease-out; transform: rotate(0deg); - &.grw-pagetree-open { + &.grw-foldertree-open { transform: rotate(90deg); } } - .grw-pagetree-title-anchor { + .grw-foldertree-title-anchor { width: 100%; overflow: hidden; text-decoration: none; } - .grw-pagetree-count-wrapper { + .grw-foldertree-count-wrapper { display: inline-block; &:hover { @@ -53,7 +53,7 @@ $grw-pagetree-item-padding-left: 10px; } } - .grw-pagetree-item-container { + .grw-foldertree-item-container { .grw-triangle-container { min-width: 35px; height: 40px; @@ -61,60 +61,60 @@ $grw-pagetree-item-padding-left: 10px; } } &:global{ - // To realize a hierarchical structure, set multiplied padding-left to each pagetree-item - > .grw-pagetree-item-container { + // To realize a hierarchical structure, set multiplied padding-left to each foldertree-item + > .grw-foldertree-item-container { > .list-group-item { padding-left: 0; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left; + padding-left: $grw-foldertree-item-padding-left; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 2; + padding-left: $grw-foldertree-item-padding-left * 2; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 3; + padding-left: $grw-foldertree-item-padding-left * 3; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 4; + padding-left: $grw-foldertree-item-padding-left * 4; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 5; + padding-left: $grw-foldertree-item-padding-left * 5; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 6; + padding-left: $grw-foldertree-item-padding-left * 6; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 7; + padding-left: $grw-foldertree-item-padding-left * 7; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 8; + padding-left: $grw-foldertree-item-padding-left * 8; } - > .grw-pagetree-item-children { - > .grw-pagetree-item-container { + > .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 9; + padding-left: $grw-foldertree-item-padding-left * 9; } - .grw-pagetree-item-children { - > .grw-pagetree-item-container { + .grw-foldertree-item-children { + > .grw-foldertree-item-container { > .list-group-item { - padding-left: $grw-pagetree-item-padding-left * 10; + padding-left: $grw-foldertree-item-padding-left * 10; } } } diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx index 8f1eb955d88..6105948cfa5 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'next-i18next'; -import { useSWRxCurrentUserBookmarkFolders } from '~/stores/bookmark'; +import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import BookmarkFolderItem from './BookmarkFolderItem'; @@ -11,19 +11,25 @@ import styles from './BookmarkFolderTree.module.scss'; const BookmarkFolderTree = (): JSX.Element => { const { t } = useTranslation(); - const { data: bookmarkFolderData, mutate: mutateBookmarkFolderData } = useSWRxCurrentUserBookmarkFolders(); + const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(true); + const [activeElement, setActiveElement] = useState(null); + const updateActiveElement = (parentId: string | null) => { + setActiveElement(parentId); + }; if (bookmarkFolderData != null) { return ( -
          +
            {bookmarkFolderData.map((item) => { return ( ); })} diff --git a/packages/app/src/components/Theme/ThemeIsland.global.scss b/packages/app/src/components/Theme/ThemeIsland.global.scss index 99ecc0b95f2..87e45f3e529 100644 --- a/packages/app/src/components/Theme/ThemeIsland.global.scss +++ b/packages/app/src/components/Theme/ThemeIsland.global.scss @@ -127,5 +127,11 @@ $color-themelight: rgba(183, 226, 219, 1); @include mixins.button-outline-svg-icon-variant($gray-400, $bgcolor-sidebar); } } + // Foldertree + .grw-foldertree { + .grw-foldertree-triangle-btn { + @include mixins.button-outline-svg-icon-variant($gray-400, $bgcolor-sidebar); + } + } } } diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index bbafe759586..58488e05ecb 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -15,7 +15,7 @@ export class InvalidParentBookmarkFolder extends ExtensibleCustomError {} export type BookmarkFolderItems = { bookmarkFolder: IBookmarkFolderDocument & HasObjectId - childCount: number + children: BookmarkFolderItems[] } export type IBookmarkFolderDocument = { @@ -32,7 +32,7 @@ export interface BookmarkFolderDocument extends Document { export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument - findParentFolderByUserId(user: Types.ObjectId | string): BookmarkFolderDocument[] + findParentFolderByUserId(user: Types.ObjectId | string, parentId: Types.ObjectId | string | null): BookmarkFolderDocument[] findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): {deletedCount: number} updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null @@ -66,8 +66,11 @@ bookmarkFolderSchema.statics.createByParameters = async function(params: IBookma return bookmarkFolder; }; -bookmarkFolderSchema.statics.findParentFolderByUserId = async function(userId: Types.ObjectId | string): Promise { - const bookmarks = this.find({ owner: userId, parent: null }) as unknown as BookmarkFolderDocument[]; +bookmarkFolderSchema.statics.findParentFolderByUserId = async function( + userId: Types.ObjectId | string, + parentId: Types.ObjectId | string | null, +): Promise { + const bookmarks = this.find({ owner: userId, parent: parentId }) as unknown as BookmarkFolderDocument[]; return bookmarks; }; diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index 39949515115..ff4a841e3a9 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -44,28 +44,15 @@ module.exports = (crowi) => { } }); - // List all main bookmark folders - router.get('/list', accessTokenParser, loginRequiredStrictly, async(req, res) => { - try { - const bookmarkFolders = await BookmarkFolder.findParentFolderByUserId(req.user?._id); - const bookmarkFolderItems = await Promise.all(bookmarkFolders.map(async bookmarkFolder => ({ - bookmarkFolder, - childCount: await BookmarkFolder.countDocuments({ parent: bookmarkFolder._id }), - }))); - return res.apiv3({ bookmarkFolderItems }); - } - catch (err) { - return res.apiv3Err(err, 500); - } - }); - - router.get('/list-child/:parentId', accessTokenParser, loginRequiredStrictly, async(req, res) => { + // List bookmark folders and child + router.get('/list/:parentId?', accessTokenParser, loginRequiredStrictly, async(req, res) => { const { parentId } = req.params; + const _parentId = parentId != null ? parentId : null; try { - const bookmarkFolders = await BookmarkFolder.findChildFolderById(parentId); + const bookmarkFolders = await BookmarkFolder.findParentFolderByUserId(req.user?._id, _parentId); const bookmarkFolderItems = await Promise.all(bookmarkFolders.map(async bookmarkFolder => ({ bookmarkFolder, - childCount: await BookmarkFolder.countDocuments({ parent: bookmarkFolder._id }), + children: await BookmarkFolder.find({ parent: bookmarkFolder._id }), }))); return res.apiv3({ bookmarkFolderItems }); } diff --git a/packages/app/src/stores/bookmark-folder.ts b/packages/app/src/stores/bookmark-folder.ts new file mode 100644 index 00000000000..de27bd54fb4 --- /dev/null +++ b/packages/app/src/stores/bookmark-folder.ts @@ -0,0 +1,16 @@ +import { Nullable } from '@growi/core'; +import { SWRResponse } from 'swr'; +import useSWRImmutable from 'swr/immutable'; + +import { apiv3Get } from '~/client/util/apiv3-client'; +import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; + +export const useSWRxBookamrkFolderAndChild = (isOpen: boolean, parentId?: Nullable): SWRResponse => { + const _parentId = parentId == null ? '' : parentId; + return useSWRImmutable( + isOpen ? `/bookmark-folder/list/${_parentId}` : null, + endpoint => apiv3Get(endpoint).then((response) => { + return response.data.bookmarkFolderItems; + }), + ); +}; diff --git a/packages/app/src/stores/bookmark.ts b/packages/app/src/stores/bookmark.ts index 2318a38bcc0..6e47c748021 100644 --- a/packages/app/src/stores/bookmark.ts +++ b/packages/app/src/stores/bookmark.ts @@ -3,7 +3,6 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; import { IPageHasId } from '~/interfaces/page'; -import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; import { apiv3Get } from '../client/util/apiv3-client'; import { IBookmarkInfo } from '../interfaces/bookmark-info'; @@ -39,21 +38,3 @@ export const useSWRxCurrentUserBookmarks = (pageNum?: Nullable): SWRResp }), ); }; - -export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse => { - return useSWRImmutable( - '/bookmark-folder/list', - endpoint => apiv3Get(endpoint).then((response) => { - return response.data.bookmarkFolderItems; - }), - ); -}; - -export const useSWRxChildBookmarkFolders = (isOpen: boolean, parentId: Nullable): SWRResponse => { - return useSWRImmutable( - isOpen && parentId != null ? `/bookmark-folder/list-child/${parentId}` : null, - endpoint => apiv3Get(endpoint).then((response) => { - return response.data.bookmarkFolderItems; - }), - ); -}; diff --git a/packages/app/src/styles/theme/_apply-colors-dark.scss b/packages/app/src/styles/theme/_apply-colors-dark.scss index 0799a2246de..9e94bfe2c43 100644 --- a/packages/app/src/styles/theme/_apply-colors-dark.scss +++ b/packages/app/src/styles/theme/_apply-colors-dark.scss @@ -316,6 +316,33 @@ ul.pagination { } } + // Foldertree + .grw-foldertree { + @include override-list-group-item-for-pagetree( + $color-sidebar-context, + lighten($bgcolor-sidebar-context, 8%), + lighten($bgcolor-sidebar-context, 15%), + darken($color-sidebar-context, 15%), + darken($color-sidebar-context, 10%), + lighten($bgcolor-sidebar-context, 18%), + lighten($bgcolor-sidebar-context, 24%) + ); + .grw-foldertree-triangle-btn { + @include mixins.button-outline-svg-icon-variant($secondary, $gray-200); + } + .btn-page-item-control { + @include button-outline-variant($gray-500, $gray-500, $secondary, transparent); + @include hover() { + background-color: lighten($bgcolor-sidebar-context, 20%); + } + &:not(:disabled):not(.disabled):active, + &:not(:disabled):not(.disabled).active { + background-color: lighten($bgcolor-sidebar-context, 34%); + } + box-shadow: none !important; + } + } + // bookmarks .grw-bookmarks-list { @include override-list-group-item-for-pagetree( diff --git a/packages/app/src/styles/theme/_apply-colors-light.scss b/packages/app/src/styles/theme/_apply-colors-light.scss index 4ec69e0bf5b..73ff2b0a010 100644 --- a/packages/app/src/styles/theme/_apply-colors-light.scss +++ b/packages/app/src/styles/theme/_apply-colors-light.scss @@ -205,6 +205,22 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active; } } + // Foldertree + .grw-foldertree { + @include override-list-group-item-for-pagetree( + $color-sidebar-context, + darken($bgcolor-sidebar-context, 5%), + darken($bgcolor-sidebar-context, 12%), + lighten($color-sidebar-context, 10%), + lighten($color-sidebar-context, 8%), + darken($bgcolor-sidebar-context, 15%), + darken($bgcolor-sidebar-context, 24%) + ); + .grw-foldertree-triangle-btn { + @include mixins.button-outline-svg-icon-variant($gray-400, $primary); + } + } + // bookmark .grw-bookmarks-list { @include override-list-group-item-for-pagetree( diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index 1962acd61e8..4369df90203 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -292,6 +292,15 @@ ul.pagination { } } } + + .grw-foldertree { + .list-group-item { + .grw-foldertree-title-anchor { + color: inherit; + } + } + } + .grw-pagetree-footer { .h5.grw-private-legacy-pages-anchor { color: inherit; diff --git a/packages/app/src/styles/theme/mixins/_list-group.scss b/packages/app/src/styles/theme/mixins/_list-group.scss index a5d34deccb6..2d61d8a254e 100644 --- a/packages/app/src/styles/theme/mixins/_list-group.scss +++ b/packages/app/src/styles/theme/mixins/_list-group.scss @@ -49,7 +49,8 @@ } } - &.grw-pagetree-current-page-item { + &.grw-pagetree-current-page-item, + &.grw-foldertree-current-folder-item { background: $bgcolor-hover; } @@ -61,7 +62,8 @@ background-color: $bgcolor-active; } } - .grw-pagetree-title-anchor { + .grw-pagetree-title-anchor, + .grw-foldertree-title-anchor { .grw-sidebar-text-muted { color: rgba(desaturate($color, 50%), 0.6); } From b5dbb04ad75d5a8d96721df6d780fa71ea8d08fd Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 28 Oct 2022 17:41:15 +0800 Subject: [PATCH 058/826] Add folder Icon component https://youtrack.weseek.co.jp/issue/GW-7895 - Add component for folder plus icon, folder and folder open icon - Implement FolderPlusIcon component to New Folder button - Add conditional rendering for folder and folder open - Implement FolderIcon to folder and children icon list --- .../app/src/components/Icons/FolderIcon.tsx | 39 +++++++++++++++++++ .../src/components/Icons/FolderPlusIcon.tsx | 18 +++++++++ .../Sidebar/Bookmarks/BookmarkFolder.tsx | 3 +- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 5 ++- 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 packages/app/src/components/Icons/FolderIcon.tsx create mode 100644 packages/app/src/components/Icons/FolderPlusIcon.tsx diff --git a/packages/app/src/components/Icons/FolderIcon.tsx b/packages/app/src/components/Icons/FolderIcon.tsx new file mode 100644 index 00000000000..e0637d1670e --- /dev/null +++ b/packages/app/src/components/Icons/FolderIcon.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +type Props = { + isOpen: boolean +} +const FolderIcon = (props: Props): JSX.Element => { + const { isOpen } = props; + + return ( + <> + {!isOpen ? ( + + + + ) : ( + + + + ) + } + + ); + +}; + +export default FolderIcon; diff --git a/packages/app/src/components/Icons/FolderPlusIcon.tsx b/packages/app/src/components/Icons/FolderPlusIcon.tsx new file mode 100644 index 00000000000..79a6fc9e5c2 --- /dev/null +++ b/packages/app/src/components/Icons/FolderPlusIcon.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const FolderPlusIcon = (): JSX.Element => ( + + + + +); + +export default FolderPlusIcon; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx index 07d880b2f59..aca25c272c3 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { useTranslation } from 'next-i18next'; import ClosableTextInput from '~/components/Common/ClosableTextInput'; +import FolderPlusIcon from '~/components/Icons/FolderPlusIcon'; import BookmarkFolderTree from './BookmarkFolderTree'; @@ -27,7 +28,7 @@ const BookmarkFolder = (props: Props): JSX.Element => { className="btn btn-block btn-outline-secondary rounded-pill d-flex justify-content-start align-middle" onClick={onClickNewFolder} > - + New Folder
        diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 3b68168c5ba..bb15422c9a2 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -5,6 +5,7 @@ import { import { useTranslation } from 'next-i18next'; import CountBadge from '~/components/Common/CountBadge'; +import FolderIcon from '~/components/Icons/FolderIcon'; import TriangleIcon from '~/components/Icons/TriangleIcon'; import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; @@ -60,11 +61,11 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt
        {
        - +
        } { -
        +

        {bookmarkFolders.bookmarkFolder.name}

        } From a09ffd15807292e17719e9e011166cc170490ab4 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 31 Oct 2022 17:42:58 +0800 Subject: [PATCH 059/826] Move and rename custom error InvalidParentBookmarkFolder https://youtrack.weseek.co.jp/issue/GW-7833 - Move and renanme InvalidParentBookmarkFolder to models/errors - Update implementation of ErrorInvalidParentBookmarkFolder --- packages/app/src/server/models/bookmark-folder.ts | 7 +++---- packages/app/src/server/models/errors.ts | 3 +++ packages/app/src/server/routes/apiv3/bookmark-folder.ts | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index fd1012e9c25..0d18bbc6948 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -1,6 +1,5 @@ import { Ref, IUser } from '@growi/core'; import { isValidObjectId } from '@growi/core/src/utils/objectid-utils'; -import ExtensibleCustomError from 'extensible-custom-error'; import { Types, Document, Model, Schema, } from 'mongoose'; @@ -9,9 +8,9 @@ import { import loggerFactory from '../../utils/logger'; import { getOrCreateModel } from '../util/mongoose-utils'; -const logger = loggerFactory('growi:models:bookmark-folder'); +import { ErrorInvalidParentBookmarkFolder } from './errors'; -export class InvalidParentBookmarkFolder extends ExtensibleCustomError {} +const logger = loggerFactory('growi:models:bookmark-folder'); export type IBookmarkFolderDocument = { @@ -54,7 +53,7 @@ bookmarkFolderSchema.statics.createByParameters = async function(params: IBookma const parentFolder = await this.findById(parent); if (!isParentFolderIdValid || parentFolder == null) { - throw new InvalidParentBookmarkFolder("Parent folder id is invalid or parent folder doesn't exists"); + throw new ErrorInvalidParentBookmarkFolder("Parent folder id is invalid or parent folder doesn't exists"); } bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id }) as unknown as BookmarkFolderDocument; } diff --git a/packages/app/src/server/models/errors.ts b/packages/app/src/server/models/errors.ts index 3019b26644e..b2f9d1bbbad 100644 --- a/packages/app/src/server/models/errors.ts +++ b/packages/app/src/server/models/errors.ts @@ -16,3 +16,6 @@ export class PathAlreadyExistsError extends ExtensibleCustomError { * User Authentication */ export class NullUsernameToBeRegisteredError extends ExtensibleCustomError {} + +// Invalid Parent bookmark folder error +export class ErrorInvalidParentBookmarkFolder extends ExtensibleCustomError {} diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index b8c17697716..5d21a5908f1 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -2,9 +2,10 @@ import { ErrorV3 } from '@growi/core'; import { body } from 'express-validator'; import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; +import { ErrorInvalidParentBookmarkFolder } from '~/server/models/errors'; import loggerFactory from '~/utils/logger'; -import BookmarkFolder, { InvalidParentBookmarkFolder } from '../../models/bookmark-folder'; +import BookmarkFolder from '../../models/bookmark-folder'; const logger = loggerFactory('growi:routes:apiv3:bookmark-folder'); @@ -37,7 +38,7 @@ module.exports = (crowi) => { return res.apiv3({ bookmarkFolder }); } catch (err) { - if (err instanceof InvalidParentBookmarkFolder) { + if (err instanceof ErrorInvalidParentBookmarkFolder) { return res.apiv3Err(new ErrorV3(err.message, 'failed_to_create_bookmark_folder')); } return res.apiv3Err(err, 500); From 1d2bbdaad02adcc5af21c4d6e49bce0bc47b7e08 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 1 Nov 2022 18:04:31 +0800 Subject: [PATCH 060/826] Change create new bookmark folder condition https://youtrack.weseek.co.jp/issue/GW-7833 - Change condition on create bookmarkFolder - Rename custom error to InvalidParentBookmarkFolderError --- packages/app/src/server/models/bookmark-folder.ts | 6 +++--- packages/app/src/server/models/errors.ts | 2 +- packages/app/src/server/routes/apiv3/bookmark-folder.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 0d18bbc6948..21ddf1266bc 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -8,7 +8,7 @@ import { import loggerFactory from '../../utils/logger'; import { getOrCreateModel } from '../util/mongoose-utils'; -import { ErrorInvalidParentBookmarkFolder } from './errors'; +import { InvalidParentBookmarkFolderError } from './errors'; const logger = loggerFactory('growi:models:bookmark-folder'); @@ -52,8 +52,8 @@ bookmarkFolderSchema.statics.createByParameters = async function(params: IBookma const isParentFolderIdValid = isValidObjectId(parent); const parentFolder = await this.findById(parent); - if (!isParentFolderIdValid || parentFolder == null) { - throw new ErrorInvalidParentBookmarkFolder("Parent folder id is invalid or parent folder doesn't exists"); + if (parentFolder != null && !isParentFolderIdValid) { + throw new InvalidParentBookmarkFolderError("Parent folder id is invalid or parent folder doesn't exists"); } bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id }) as unknown as BookmarkFolderDocument; } diff --git a/packages/app/src/server/models/errors.ts b/packages/app/src/server/models/errors.ts index b2f9d1bbbad..0fada312864 100644 --- a/packages/app/src/server/models/errors.ts +++ b/packages/app/src/server/models/errors.ts @@ -18,4 +18,4 @@ export class PathAlreadyExistsError extends ExtensibleCustomError { export class NullUsernameToBeRegisteredError extends ExtensibleCustomError {} // Invalid Parent bookmark folder error -export class ErrorInvalidParentBookmarkFolder extends ExtensibleCustomError {} +export class InvalidParentBookmarkFolderError extends ExtensibleCustomError {} diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index 5d21a5908f1..278b98a3406 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -2,7 +2,7 @@ import { ErrorV3 } from '@growi/core'; import { body } from 'express-validator'; import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; -import { ErrorInvalidParentBookmarkFolder } from '~/server/models/errors'; +import { InvalidParentBookmarkFolderError } from '~/server/models/errors'; import loggerFactory from '~/utils/logger'; import BookmarkFolder from '../../models/bookmark-folder'; @@ -38,7 +38,7 @@ module.exports = (crowi) => { return res.apiv3({ bookmarkFolder }); } catch (err) { - if (err instanceof ErrorInvalidParentBookmarkFolder) { + if (err instanceof InvalidParentBookmarkFolderError) { return res.apiv3Err(new ErrorV3(err.message, 'failed_to_create_bookmark_folder')); } return res.apiv3Err(err, 500); From 6cb890620d3144788b66f2815452097a160a103d Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 2 Nov 2022 17:13:24 +0800 Subject: [PATCH 061/826] Improve code https://youtrack.weseek.co.jp/issue/GW-7833 - Move and rename IBookmarkFolderDocument to IBookmarkFolder - Update Implementation of IBookmarkFolder - Update parent references in BookmarkFolderDocument - Update create bookmark folder condition - Update statics method for find bookmark folder and child --- packages/app/src/interfaces/bookmark-info.ts | 6 +++ .../app/src/server/models/bookmark-folder.ts | 39 +++++++++---------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/packages/app/src/interfaces/bookmark-info.ts b/packages/app/src/interfaces/bookmark-info.ts index 49a3e44f564..6641885a085 100644 --- a/packages/app/src/interfaces/bookmark-info.ts +++ b/packages/app/src/interfaces/bookmark-info.ts @@ -17,3 +17,9 @@ type BookmarkedPage = { } export type MyBookmarkList = BookmarkedPage[] + +export interface IBookmarkFolder { + name: string + owner: Ref + parent?: Ref +} diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 21ddf1266bc..85e2798b7e8 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -1,9 +1,10 @@ -import { Ref, IUser } from '@growi/core'; import { isValidObjectId } from '@growi/core/src/utils/objectid-utils'; import { Types, Document, Model, Schema, } from 'mongoose'; +import { IBookmarkFolder } from '~/interfaces/bookmark-info'; + import loggerFactory from '../../utils/logger'; import { getOrCreateModel } from '../util/mongoose-utils'; @@ -13,22 +14,17 @@ import { InvalidParentBookmarkFolderError } from './errors'; const logger = loggerFactory('growi:models:bookmark-folder'); -export type IBookmarkFolderDocument = { - name: string - owner: Ref - parent?: string -} export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId name: string owner: Types.ObjectId - parent?: Types.ObjectId | null + parent?: [this] } export interface BookmarkFolderModel extends Model{ - createByParameters(params: IBookmarkFolderDocument): IBookmarkFolderDocument - findParentFolderByUserId(user: Types.ObjectId | string): IBookmarkFolderDocument[] - findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise + createByParameters(params: IBookmarkFolder): BookmarkFolderDocument + findParentFolderByUserId(user: Types.ObjectId | string): BookmarkFolderDocument[] + findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): {deletedCount: number} updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null } @@ -40,35 +36,38 @@ const bookmarkFolderSchema = new Schema { +bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolder): Promise { const { name, owner, parent } = params; let bookmarkFolder; - if (parent === null) { - bookmarkFolder = await this.create({ name, owner, parent: null }) as unknown as BookmarkFolderDocument; + if (parent == null) { + bookmarkFolder = await this.create({ name, owner }) as unknown as BookmarkFolderDocument; } else { // Check if parent folder id is valid and parent folder exists - const isParentFolderIdValid = isValidObjectId(parent); - const parentFolder = await this.findById(parent); + const isParentFolderIdValid = isValidObjectId(parent as string); - if (parentFolder != null && !isParentFolderIdValid) { - throw new InvalidParentBookmarkFolderError("Parent folder id is invalid or parent folder doesn't exists"); + if (!isParentFolderIdValid) { + throw new InvalidParentBookmarkFolderError('Parent folder id is invalid'); + } + const parentFolder = await this.findById(parent); + if (parentFolder == null) { + throw new InvalidParentBookmarkFolderError('Parent folder not found'); } - bookmarkFolder = await this.create({ name, owner, parent: parentFolder?._id }) as unknown as BookmarkFolderDocument; + bookmarkFolder = await this.create({ name, owner, parent: parentFolder._id }) as unknown as BookmarkFolderDocument; } return bookmarkFolder; }; bookmarkFolderSchema.statics.findParentFolderByUserId = async function(userId: Types.ObjectId | string): Promise { - const bookmarks = this.find({ owner: userId, parent: null }) as unknown as BookmarkFolderDocument[]; + const bookmarks = this.find({ owner: userId, parent: { $exists: false } }) as unknown as BookmarkFolderDocument[]; return bookmarks; }; bookmarkFolderSchema.statics.findChildFolderById = async function(parentFolderId: Types.ObjectId | string): Promise { const parentFolder = await this.findById(parentFolderId) as unknown as BookmarkFolderDocument; - const childFolders = await this.find({ parent: parentFolder._id }); + const childFolders = await this.find({ parent: parentFolder }); return childFolders; }; From f7a1c08871349be2b3913177a61386cb838d3872 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 8 Nov 2022 10:49:46 +0800 Subject: [PATCH 062/826] Improve query and rename method https://youtrack.weseek.co.jp/issue/GW-7833 - Rename findParentFolderByUserId to findFolderAndChildren - Add new interface of BookmarkFolderItems - Add virtual method to get children folder - Update findFolderAndChildren method --- .../app/src/server/models/bookmark-folder.ts | 23 +++++++++++++++---- .../server/routes/apiv3/bookmark-folder.ts | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 85e2798b7e8..55b7c6b4278 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -13,7 +13,11 @@ import { InvalidParentBookmarkFolderError } from './errors'; const logger = loggerFactory('growi:models:bookmark-folder'); - +export interface BookmarkFolderItems { + _id: string + name: string + children: this[] +} export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId name: string @@ -23,7 +27,7 @@ export interface BookmarkFolderDocument extends Document { export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolder): BookmarkFolderDocument - findParentFolderByUserId(user: Types.ObjectId | string): BookmarkFolderDocument[] + findFolderAndChildren(user: Types.ObjectId | string, parentId?: Types.ObjectId | string): BookmarkFolderItems[] findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): {deletedCount: number} updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null @@ -35,6 +39,11 @@ const bookmarkFolderSchema = new Schema { const { name, owner, parent } = params; @@ -60,8 +69,12 @@ bookmarkFolderSchema.statics.createByParameters = async function(params: IBookma return bookmarkFolder; }; -bookmarkFolderSchema.statics.findParentFolderByUserId = async function(userId: Types.ObjectId | string): Promise { - const bookmarks = this.find({ owner: userId, parent: { $exists: false } }) as unknown as BookmarkFolderDocument[]; +bookmarkFolderSchema.statics.findFolderAndChildren = async function( + userId: Types.ObjectId | string, + parentId?: Types.ObjectId | string, +): Promise { + const parentFolder = await this.findById(parentId) as unknown as BookmarkFolderDocument; + const bookmarks = await this.find({ owner: userId, parent: parentFolder }).populate({ path: 'children' }).exec() as unknown as BookmarkFolderItems[]; return bookmarks; }; @@ -94,4 +107,6 @@ bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolde }; +bookmarkFolderSchema.set('toObject', { virtuals: true }); + export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema); diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index 278b98a3406..8773587f86c 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -48,7 +48,7 @@ module.exports = (crowi) => { // List all main bookmark folders router.get('/list', accessTokenParser, loginRequiredStrictly, async(req, res) => { try { - const bookmarkFolders = await BookmarkFolder.findParentFolderByUserId(req.user?._id); + const bookmarkFolders = await BookmarkFolder.findFolderAndChildren(req.user?._id); return res.apiv3({ bookmarkFolders }); } catch (err) { From 69fb322ae3b572369df4bce8b0c4a46a70be50fd Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 8 Nov 2022 12:02:05 +0800 Subject: [PATCH 063/826] Fix query https://youtrack.weseek.co.jp/issue/GW-7895 - Update bookmarkFolderItems return value - Adjust props of BookmarkFolderItem component - Change hasChildren to method --- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 48 ++++++++++--------- .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 14 ++---- .../server/routes/apiv3/bookmark-folder.ts | 11 +++-- packages/app/src/stores/bookmark-folder.ts | 2 +- 4 files changed, 35 insertions(+), 40 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index bb15422c9a2..dc89429f320 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -12,42 +12,45 @@ import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; type BookmarkFolderItemProps = { - bookmarkFolders: BookmarkFolderItems + bookmarkFolder: BookmarkFolderItems isOpen?: boolean - updateActiveElement?: (parentId: string | null) => void - isActive?: boolean } const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { const { - bookmarkFolders, isOpen: _isOpen = false, updateActiveElement, isActive, + bookmarkFolder, isOpen: _isOpen = false, } = props; const { t } = useTranslation(); - const hasChildren = bookmarkFolders.children.length > 0; const [currentParentFolder, setCurrentParentFolder] = useState(null); const [isOpen, setIsOpen] = useState(_isOpen); const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(isOpen, currentParentFolder); + const { name, _id: parentId, children } = bookmarkFolder; + + const hasChildren = useCallback((): boolean => { + if (children != null) { + return children.length > 0; + } + return false; + }, [children]); useEffect(() => { - setCurrentParentFolder(bookmarkFolders.bookmarkFolder._id); - }, [bookmarkFolders]); + setCurrentParentFolder(parentId); + }, [bookmarkFolder, parentId]); const loadChildFolder = useCallback(async() => { - setCurrentParentFolder(bookmarkFolders.bookmarkFolder._id); + setCurrentParentFolder(parentId); setIsOpen(!isOpen); - updateActiveElement?.(!isOpen ? bookmarkFolders.bookmarkFolder._id : null); mutateChildBookmarkData(); - }, [bookmarkFolders, isOpen, updateActiveElement, mutateChildBookmarkData]); + }, [parentId, isOpen, mutateChildBookmarkData]); return ( -
        -
      • - {hasChildren && ( + {hasChildren() && (
      • { - isOpen && hasChildren && childBookmarkFolderData?.map(children => ( -
        + isOpen && hasChildren() && childBookmarkFolderData?.map(children => ( +
        )) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx index 6105948cfa5..582a3e32cdd 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useTranslation } from 'next-i18next'; @@ -12,24 +12,16 @@ import styles from './BookmarkFolderTree.module.scss'; const BookmarkFolderTree = (): JSX.Element => { const { t } = useTranslation(); const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(true); - const [activeElement, setActiveElement] = useState(null); - - const updateActiveElement = (parentId: string | null) => { - setActiveElement(parentId); - }; if (bookmarkFolderData != null) { return ( -
          {bookmarkFolderData.map((item) => { return ( ); })} diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index a01c28c5cb9..33f5b0bb8e6 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -48,13 +48,14 @@ module.exports = (crowi) => { // List bookmark folders and child router.get('/list/:parentId?', accessTokenParser, loginRequiredStrictly, async(req, res) => { const { parentId } = req.params; - const _parentId = parentId != null ? parentId : null; + const _parentId = parentId ?? null; try { const bookmarkFolders = await BookmarkFolder.findFolderAndChildren(req.user?._id, _parentId); - const bookmarkFolderItems = await Promise.all(bookmarkFolders.map(async bookmarkFolder => ({ - bookmarkFolder, - children: await BookmarkFolder.find({ parent: bookmarkFolder._id }), - }))); + const bookmarkFolderItems = bookmarkFolders.map(bookmarkFolder => ({ + _id: bookmarkFolder._id, + name: bookmarkFolder.name, + children: bookmarkFolder.children, + })); return res.apiv3({ bookmarkFolderItems }); } catch (err) { diff --git a/packages/app/src/stores/bookmark-folder.ts b/packages/app/src/stores/bookmark-folder.ts index de27bd54fb4..69496a0e153 100644 --- a/packages/app/src/stores/bookmark-folder.ts +++ b/packages/app/src/stores/bookmark-folder.ts @@ -6,7 +6,7 @@ import { apiv3Get } from '~/client/util/apiv3-client'; import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; export const useSWRxBookamrkFolderAndChild = (isOpen: boolean, parentId?: Nullable): SWRResponse => { - const _parentId = parentId == null ? '' : parentId; + const _parentId = parentId ?? ''; return useSWRImmutable( isOpen ? `/bookmark-folder/list/${_parentId}` : null, endpoint => apiv3Get(endpoint).then((response) => { From 88af3f1099aeb2e8dc23237243fa652d80690cd8 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 8 Nov 2022 17:37:41 +0800 Subject: [PATCH 064/826] Adjust bookmark item and tree https://youtrack.weseek.co.jp/issue/GW-7904 - Remove props of BookmarkFolder - Implement onClickBookmarkFolder and onPressEnterHandler in BookmarkFolder - Add ne component BookmarkFolderNameInput - Update initial data fetching in BookmarkFolderTree - Update useSWRxBookamrkFolderAndChild method --- .../app/src/components/Sidebar/Bookmarks.tsx | 35 +---- .../Sidebar/Bookmarks/BookmarkFolder.tsx | 55 +++++--- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 122 +++++++++++++----- .../Bookmarks/BookmarkFolderNameInput.tsx | 43 ++++++ .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 5 +- packages/app/src/stores/bookmark-folder.ts | 6 +- 6 files changed, 177 insertions(+), 89 deletions(-) create mode 100644 packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index eb96fa2242e..6dc59bfcdb3 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -1,14 +1,12 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { toastSuccess, toastError } from '~/client/util/apiNotification'; -import { apiv3Post } from '~/client/util/apiv3-client'; +import { toastSuccess } from '~/client/util/apiNotification'; import { IPageToDeleteWithMeta } from '~/interfaces/page'; import { OnDeletedFunction } from '~/interfaces/ui'; import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; -import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import { useIsGuestUser } from '~/stores/context'; import { usePageDeleteModal } from '~/stores/modal'; @@ -20,11 +18,8 @@ const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); - const { mutate: mutateInitialBookmarkFolderData } = useSWRxBookamrkFolderAndChild(true); const { open: openDeleteModal } = usePageDeleteModal(); - const [isRenameFolderShown, setIsRenameFolderShown] = useState(false); - const [folderName, setFolderName] = useState(''); const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { @@ -44,24 +39,6 @@ const Bookmarks = () : JSX.Element => { openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); }; - const onPressEnterHandler = async(folderName: string) => { - setFolderName(folderName); - try { - await apiv3Post('/bookmark-folder', { name: folderName, parent: null }); - setIsRenameFolderShown(false); - setFolderName(''); - mutateInitialBookmarkFolderData(); - toastSuccess(t('Create New Bookmark Folder Success')); - } - catch (err) { - toastError(err); - } - }; - - const onClickOutsideHandler = () => { - setIsRenameFolderShown(false); - setFolderName(''); - }; const renderBookmarkList = () => { if (currentUserBookmarksData?.length === 0) { @@ -97,13 +74,7 @@ const Bookmarks = () : JSX.Element => {
        {!isGuestUser && ( <> - setIsRenameFolderShown(true)} - isRenameInputShown={isRenameFolderShown} - onClickOutside={onClickOutsideHandler} - onPressEnter={onPressEnterHandler} - folderName={folderName} - /> + ) } diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx index aca25c272c3..0bb183643a0 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx @@ -1,32 +1,47 @@ - -import React from 'react'; +import React, { useCallback, useState } from 'react'; import { useTranslation } from 'next-i18next'; -import ClosableTextInput from '~/components/Common/ClosableTextInput'; +import { toastError, toastSuccess } from '~/client/util/apiNotification'; +import { apiv3Post } from '~/client/util/apiv3-client'; import FolderPlusIcon from '~/components/Icons/FolderPlusIcon'; +import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; +import BookmarkFolderNameInput from './BookmarkFolderNameInput'; import BookmarkFolderTree from './BookmarkFolderTree'; -type Props = { - onClickNewFolder: () => void - isRenameInputShown: boolean - folderName: string - onClickOutside: () => void - onPressEnter: (folderName: string) => void -} -const BookmarkFolder = (props: Props): JSX.Element => { - const { - onClickNewFolder, isRenameInputShown, folderName, onClickOutside, onPressEnter, - } = props; + +const BookmarkFolder = (): JSX.Element => { + const { t } = useTranslation(); + const [isRenameInputShown, setIsRenameInputShown] = useState(false); + const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); + + + const onClickBookmarkFolder = () => { + setIsRenameInputShown(true); + }; + + const onPressEnterHandler = useCallback(async(folderName: string) => { + + try { + await apiv3Post('/bookmark-folder', { name: folderName, parent: null }); + await mutateChildBookmarkData(); + setIsRenameInputShown(false); + toastSuccess(t('Create New Bookmark Folder Success')); + } + catch (err) { + toastError(err); + } + + }, [mutateChildBookmarkData, t]); return ( <>
        { isRenameInputShown && ( -
        - + setIsRenameInputShown(false)} + onPressEnter={onPressEnterHandler} />
        ) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index dc89429f320..74bc121bce7 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -4,51 +4,101 @@ import { import { useTranslation } from 'next-i18next'; +import { toastError, toastSuccess } from '~/client/util/apiNotification'; +import { apiv3Post } from '~/client/util/apiv3-client'; import CountBadge from '~/components/Common/CountBadge'; import FolderIcon from '~/components/Icons/FolderIcon'; import TriangleIcon from '~/components/Icons/TriangleIcon'; import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; +import BookmarkFolderNameInput from './BookmarkFolderNameInput'; + type BookmarkFolderItemProps = { bookmarkFolder: BookmarkFolderItems isOpen?: boolean } const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { - const { - bookmarkFolder, isOpen: _isOpen = false, - } = props; + const { bookmarkFolder, isOpen: _isOpen = false } = props; + const { t } = useTranslation(); - const [currentParentFolder, setCurrentParentFolder] = useState(null); - const [isOpen, setIsOpen] = useState(_isOpen); - const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(isOpen, currentParentFolder); const { name, _id: parentId, children } = bookmarkFolder; + const [currentChildren, setCurrentChildren] = useState(); + const [isRenameInputShown, setIsRenameInputShown] = useState(false); + const [currentParentFolder, setCurrentParentFolder] = useState(parentId); + const [isOpen, setIsOpen] = useState(_isOpen); + const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(isOpen ? currentParentFolder : null); - const hasChildren = useCallback((): boolean => { - if (children != null) { - return children.length > 0; + + const childCount = useCallback((): number => { + if (currentChildren != null && currentChildren.length > children.length) { + return currentChildren.length; } - return false; - }, [children]); + return children.length; + }, [children.length, currentChildren]); useEffect(() => { - setCurrentParentFolder(parentId); - }, [bookmarkFolder, parentId]); + if (isOpen && childBookmarkFolderData != null) { + mutateChildBookmarkData(); + setCurrentChildren(childBookmarkFolderData); + } + }, [childBookmarkFolderData, isOpen, mutateChildBookmarkData]); + + const hasChildren = useCallback((): boolean => { + if (currentChildren != null && currentChildren.length > children.length) { + return currentChildren.length > 0; + } + return children.length > 0; + }, [children.length, currentChildren]); + const loadChildFolder = useCallback(async() => { - setCurrentParentFolder(parentId); setIsOpen(!isOpen); - mutateChildBookmarkData(); - }, [parentId, isOpen, mutateChildBookmarkData]); + setCurrentParentFolder(bookmarkFolder._id); + }, [bookmarkFolder, isOpen]); + + + const onPressEnterHandler = useCallback(async(folderName: string) => { + + try { + await apiv3Post('/bookmark-folder', { name: folderName, parent: currentParentFolder }); + setIsOpen(true); + setIsRenameInputShown(false); + mutateChildBookmarkData(); + toastSuccess(t('Create New Bookmark Folder Success')); + } + catch (err) { + toastError(err); + } + + }, [currentParentFolder, mutateChildBookmarkData, t]); + + const onClickPlusButton = useCallback(async() => { + if (!isOpen && hasChildren()) { + setIsOpen(true); + } + setIsRenameInputShown(true); + }, [hasChildren, isOpen]); + + const RenderChildFolder = () => { + return isOpen && currentChildren?.map((childFolder) => { + return ( +
        + +
        + ); + }); + }; return (
        -
      • +
      • {hasChildren() && (
        } { -
        +

        {name}

        } {hasChildren() && (
        - +
        )} +
        + + + + +
      • + {isRenameInputShown && ( +
        + setIsRenameInputShown(false)} + onPressEnter={onPressEnterHandler} + /> +
        + )} { - isOpen && hasChildren() && childBookmarkFolderData?.map(children => ( -
        - -
        - )) + RenderChildFolder() }
        ); diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx new file mode 100644 index 00000000000..2abd0b5dac2 --- /dev/null +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx @@ -0,0 +1,43 @@ +import { useTranslation } from 'next-i18next'; + +import ClosableTextInput, { AlertInfo, AlertType } from '~/components/Common/ClosableTextInput'; + + +type Props = { + onClickOutside: () => void + onPressEnter: (folderName: string) => void + value?: string +} + +const BookmarkFolderNameInput = (props: Props): JSX.Element => { + const { + onClickOutside, onPressEnter, value, + } = props; + const { t } = useTranslation(); + + + const inputValidator = (title: string | null): AlertInfo | null => { + if (title == null || title === '' || title.trim() === '') { + return { + type: AlertType.WARNING, + message: t('form_validation.title_required'), + }; + } + return null; + }; + + return ( +
        + +
        + ); +}; + + +export default BookmarkFolderNameInput; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx index 582a3e32cdd..564b0ecbb0c 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useTranslation } from 'next-i18next'; @@ -11,10 +10,10 @@ import styles from './BookmarkFolderTree.module.scss'; const BookmarkFolderTree = (): JSX.Element => { const { t } = useTranslation(); - const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(true); - + const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(); if (bookmarkFolderData != null) { return ( +
          {bookmarkFolderData.map((item) => { return ( diff --git a/packages/app/src/stores/bookmark-folder.ts b/packages/app/src/stores/bookmark-folder.ts index 69496a0e153..b058ad20887 100644 --- a/packages/app/src/stores/bookmark-folder.ts +++ b/packages/app/src/stores/bookmark-folder.ts @@ -5,10 +5,10 @@ import useSWRImmutable from 'swr/immutable'; import { apiv3Get } from '~/client/util/apiv3-client'; import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; -export const useSWRxBookamrkFolderAndChild = (isOpen: boolean, parentId?: Nullable): SWRResponse => { - const _parentId = parentId ?? ''; +export const useSWRxBookamrkFolderAndChild = (parentId?: Nullable): SWRResponse => { + const _parentId = parentId == null ? '' : parentId; return useSWRImmutable( - isOpen ? `/bookmark-folder/list/${_parentId}` : null, + `/bookmark-folder/list/${_parentId}`, endpoint => apiv3Get(endpoint).then((response) => { return response.data.bookmarkFolderItems; }), From d57eb45e8b67d488b15b79fb7d9a1e5cb5a87db9 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 9 Nov 2022 18:20:11 +0800 Subject: [PATCH 065/826] Add component for action menu button https://youtrack.weseek.co.jp/issue/GW-7905 - Add component BookmarkFolderItemControl for rename and delete folder - Show rename input on rename action click - Adjust some variable name - Remove mutation on press enter - Add parent types definition to BookmarkFolderItems - Update return value of bookmark folder list route --- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 58 +++++++++++++------ .../Bookmarks/BookmarkFolderItemControl.tsx | 49 ++++++++++++++++ .../app/src/server/models/bookmark-folder.ts | 1 + .../server/routes/apiv3/bookmark-folder.ts | 1 + 4 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItemControl.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 74bc121bce7..4829f73e22d 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -3,6 +3,7 @@ import { } from 'react'; import { useTranslation } from 'next-i18next'; +import { DropdownToggle } from 'reactstrap'; import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Post } from '~/client/util/apiv3-client'; @@ -12,6 +13,7 @@ import TriangleIcon from '~/components/Icons/TriangleIcon'; import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; +import BookmarkFolderItemControl from './BookmarkFolderItemControl'; import BookmarkFolderNameInput from './BookmarkFolderNameInput'; @@ -23,13 +25,15 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt const { bookmarkFolder, isOpen: _isOpen = false } = props; const { t } = useTranslation(); - const { name, _id: parentId, children } = bookmarkFolder; + const { + name, _id: folderId, children, parent, + } = bookmarkFolder; const [currentChildren, setCurrentChildren] = useState(); const [isRenameInputShown, setIsRenameInputShown] = useState(false); - const [currentParentFolder, setCurrentParentFolder] = useState(parentId); + const [currentParentFolder, setCurrentParentFolder] = useState(folderId); const [isOpen, setIsOpen] = useState(_isOpen); const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(isOpen ? currentParentFolder : null); - + const [isRenameAction, setIsRenameAction] = useState(false); const childCount = useCallback((): number => { if (currentChildren != null && currentChildren.length > children.length) { @@ -55,8 +59,8 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt const loadChildFolder = useCallback(async() => { setIsOpen(!isOpen); - setCurrentParentFolder(bookmarkFolder._id); - }, [bookmarkFolder, isOpen]); + setCurrentParentFolder(folderId); + }, [folderId, isOpen]); const onPressEnterHandler = useCallback(async(folderName: string) => { @@ -65,14 +69,13 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt await apiv3Post('/bookmark-folder', { name: folderName, parent: currentParentFolder }); setIsOpen(true); setIsRenameInputShown(false); - mutateChildBookmarkData(); toastSuccess(t('Create New Bookmark Folder Success')); } catch (err) { toastError(err); } - }, [currentParentFolder, mutateChildBookmarkData, t]); + }, [currentParentFolder, t]); const onClickPlusButton = useCallback(async() => { if (!isOpen && hasChildren()) { @@ -94,9 +97,12 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt }); }; + const onClickRenameHandler = useCallback(() => { + setIsRenameAction(true); + }, []); return ( -
        • @@ -117,19 +123,33 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt
          } - { -
          -

          {name}

          -
          + { isRenameAction ? ( + setIsRenameAction(false)} + onPressEnter={onPressEnterHandler} value={name} + /> + ) : ( + <> +
          +

          {name}

          +
          + {hasChildren() && ( +
          + +
          + )} + + ) + } - {hasChildren() && ( -
          - -
          - )}
          - - + + + + + + + + + ); +}; + +export default DeleteBookmarkFolderModal; From d0d02a16cc0d85a3cab4e8a016f1a2ea4bfad3ea Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 11 Nov 2022 19:15:48 +0800 Subject: [PATCH 067/826] Add parentFolder virtual https://youtrack.weseek.co.jp/issue/GW-7905 - Update BookmarkFolderItems interface - Add virtual definition of parentFolder - Populate virtual parentFolder object - --- packages/app/src/server/models/bookmark-folder.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index c111e050b20..b8a32180b4b 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -16,6 +16,7 @@ const logger = loggerFactory('growi:models:bookmark-folder'); export interface BookmarkFolderItems { _id: string name: string + parentFolder: this parent: string children: this[] } @@ -46,6 +47,13 @@ bookmarkFolderSchema.virtual('children', { foreignField: 'parent', }); +bookmarkFolderSchema.virtual('parentFolder', { + ref: 'BookmarkFolder', + localField: 'parent', + foreignField: '_id', + justOne: true, +}); + bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolder): Promise { const { name, owner, parent } = params; let bookmarkFolder; @@ -75,7 +83,9 @@ bookmarkFolderSchema.statics.findFolderAndChildren = async function( parentId?: Types.ObjectId | string, ): Promise { const parentFolder = await this.findById(parentId) as unknown as BookmarkFolderDocument; - const bookmarks = await this.find({ owner: userId, parent: parentFolder }).populate({ path: 'children' }).exec() as unknown as BookmarkFolderItems[]; + const populatePaths = [{ path: 'children' }, { path: 'parentFolder' }]; + const bookmarks = await this.find({ owner: userId, parent: parentFolder }) + .populate(populatePaths).exec() as unknown as BookmarkFolderItems[]; return bookmarks; }; From 04dfa538b810a8aeeaaee663f40d1f956c8bbbd6 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 15 Nov 2022 17:52:46 +0800 Subject: [PATCH 068/826] Fix reload after rename and delete https://youtrack.weseek.co.jp/issue/GW-7905 - Adjust mutation for children and parent folder reload - Rename curentParentFolder to targetFolder - Adjust loadParent method - Remove isLoadparent state - Move BookmarkFolderItems interface to bookmark-info - Remove unused virtual definition and population of parentFolder --- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 31 +++++++++---------- packages/app/src/interfaces/bookmark-info.ts | 7 +++++ .../app/src/server/models/bookmark-folder.ts | 18 ++--------- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index e2c3005db33..314a2b2024f 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -10,7 +10,7 @@ import { apiv3Delete, apiv3Post, apiv3Put } from '~/client/util/apiv3-client'; import CountBadge from '~/components/Common/CountBadge'; import FolderIcon from '~/components/Icons/FolderIcon'; import TriangleIcon from '~/components/Icons/TriangleIcon'; -import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; +import { BookmarkFolderItems } from '~/interfaces/bookmark-info'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import BookmarkFolderItemControl from './BookmarkFolderItemControl'; @@ -31,10 +31,10 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt } = bookmarkFolder; const [currentChildren, setCurrentChildren] = useState(); const [isRenameInputShown, setIsRenameInputShown] = useState(false); - const [currentParentFolder, setCurrentParentFolder] = useState(folderId); + const [targetFolder, setTargetFolder] = useState(folderId); const [isOpen, setIsOpen] = useState(_isOpen); - const [isLoadParent, setIsLoadParent] = useState(false); - const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(isOpen || isLoadParent ? currentParentFolder : null); + const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(targetFolder); + const { mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild(parent); const [isRenameAction, setIsRenameAction] = useState(false); const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState(false); @@ -46,14 +46,12 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt }, [children.length, currentChildren]); useEffect(() => { - if (isOpen && childBookmarkFolderData != null) { + if (isOpen) { mutateChildBookmarkData(); setCurrentChildren(childBookmarkFolderData); } - else if (isLoadParent) { - mutateChildBookmarkData(); - } - }, [childBookmarkFolderData, isLoadParent, isOpen, mutateChildBookmarkData]); + }, [childBookmarkFolderData, isOpen, mutateChildBookmarkData]); + const hasChildren = useCallback((): boolean => { if (currentChildren != null && currentChildren.length > children.length) { @@ -64,14 +62,12 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt const loadChildFolder = useCallback(async() => { setIsOpen(!isOpen); - setIsLoadParent(false); - setCurrentParentFolder(folderId); + setTargetFolder(folderId); }, [folderId, isOpen]); const loadParent = useCallback(async() => { - setCurrentParentFolder(parent); - setIsLoadParent(true); - }, [parent]); + mutateParentBookmarkFolder(); + }, [mutateParentBookmarkFolder]); const onPressEnterHandler = useCallback(async(folderName: string) => { try { @@ -82,7 +78,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt toastSuccess(t('Rename Bookmark Folder Success')); } else { - await apiv3Post('/bookmark-folder', { name: folderName, parent: currentParentFolder }); + await apiv3Post('/bookmark-folder', { name: folderName, parent: targetFolder }); setIsOpen(true); setIsRenameInputShown(false); mutateChildBookmarkData(); @@ -93,7 +89,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt toastError(err); } - }, [currentParentFolder, folderId, isRenameAction, loadParent, mutateChildBookmarkData, parent, t]); + }, [folderId, isRenameAction, loadParent, mutateChildBookmarkData, parent, t, targetFolder]); const onClickPlusButton = useCallback(async() => { if (!isOpen && hasChildren()) { @@ -131,13 +127,14 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt try { await apiv3Delete(`/bookmark-folder/${folderId}`); setIsDeleteFolderModalShown(false); + loadParent(); toastSuccess(t('Delete bookmark folder success')); } catch (err) { toastError(err); } - }, [folderId, t]); + }, [folderId, loadParent, t]); return (
          parent?: Ref } + +export interface BookmarkFolderItems { + _id: string + name: string + parent: string + children: this[] +} diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index b8a32180b4b..c538f7f8891 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -3,7 +3,7 @@ import { Types, Document, Model, Schema, } from 'mongoose'; -import { IBookmarkFolder } from '~/interfaces/bookmark-info'; +import { IBookmarkFolder, BookmarkFolderItems } from '~/interfaces/bookmark-info'; import loggerFactory from '../../utils/logger'; @@ -13,13 +13,6 @@ import { InvalidParentBookmarkFolderError } from './errors'; const logger = loggerFactory('growi:models:bookmark-folder'); -export interface BookmarkFolderItems { - _id: string - name: string - parentFolder: this - parent: string - children: this[] -} export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId name: string @@ -47,12 +40,6 @@ bookmarkFolderSchema.virtual('children', { foreignField: 'parent', }); -bookmarkFolderSchema.virtual('parentFolder', { - ref: 'BookmarkFolder', - localField: 'parent', - foreignField: '_id', - justOne: true, -}); bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolder): Promise { const { name, owner, parent } = params; @@ -83,9 +70,8 @@ bookmarkFolderSchema.statics.findFolderAndChildren = async function( parentId?: Types.ObjectId | string, ): Promise { const parentFolder = await this.findById(parentId) as unknown as BookmarkFolderDocument; - const populatePaths = [{ path: 'children' }, { path: 'parentFolder' }]; const bookmarks = await this.find({ owner: userId, parent: parentFolder }) - .populate(populatePaths).exec() as unknown as BookmarkFolderItems[]; + .populate({ path: 'children' }).exec() as unknown as BookmarkFolderItems[]; return bookmarks; }; From 1b38a8e3857e57b8d8cd90b3e89625db1726cf40 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 15 Nov 2022 18:07:44 +0800 Subject: [PATCH 069/826] Update bookmark-folder.ts - Update BookmarkFolderItems import --- packages/app/src/stores/bookmark-folder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/stores/bookmark-folder.ts b/packages/app/src/stores/bookmark-folder.ts index b058ad20887..8c80b811035 100644 --- a/packages/app/src/stores/bookmark-folder.ts +++ b/packages/app/src/stores/bookmark-folder.ts @@ -3,7 +3,7 @@ import { SWRResponse } from 'swr'; import useSWRImmutable from 'swr/immutable'; import { apiv3Get } from '~/client/util/apiv3-client'; -import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; +import { BookmarkFolderItems } from '~/interfaces/bookmark-info'; export const useSWRxBookamrkFolderAndChild = (parentId?: Nullable): SWRResponse => { const _parentId = parentId == null ? '' : parentId; From 43e935b886b6c634f6a7162ac12cd9909489fd6c Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 15 Nov 2022 18:13:13 +0800 Subject: [PATCH 070/826] Change method to camel case https://youtrack.weseek.co.jp/issue/GW-7904 - Change RenderChildFolder method name to camel case --- .../src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 74bc121bce7..050f0108d3e 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -81,7 +81,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt setIsRenameInputShown(true); }, [hasChildren, isOpen]); - const RenderChildFolder = () => { + const renderChildFolder = () => { return isOpen && currentChildren?.map((childFolder) => { return (
          @@ -150,7 +150,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt
          )} { - RenderChildFolder() + renderChildFolder() }
          ); From 9f630a5d8c3094ff06d8a287288d32bae517236d Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 15 Nov 2022 18:20:43 +0800 Subject: [PATCH 071/826] Update DeleteBookmarkFolderModal.tsx --- .../components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx b/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx index 26914682b45..dec1e963f9d 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx @@ -7,7 +7,7 @@ import { } from 'reactstrap'; import FolderIcon from '~/components/Icons/FolderIcon'; -import { BookmarkFolderItems } from '~/server/models/bookmark-folder'; +import { BookmarkFolderItems } from '~/interfaces/bookmark-info'; type DeleteBookmarkFolderModalProps = { isOpen: boolean From fb2166bd758f6956ba6be9aa5d653d086812b231 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 16 Nov 2022 16:07:34 +0800 Subject: [PATCH 072/826] Implement i18n https://youtrack.weseek.co.jp/issue/GW-7905 - Add translation for bookmark folder - Implement i18n to all Bookmark folder component --- .../public/static/locales/en_US/translation.json | 14 +++++++++++++- .../public/static/locales/ja_JP/translation.json | 14 +++++++++++++- .../public/static/locales/zh_CN/translation.json | 14 +++++++++++++- .../Sidebar/Bookmarks/BookmarkFolder.tsx | 4 ++-- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 8 +++++--- .../Sidebar/Bookmarks/BookmarkFolderNameInput.tsx | 2 +- .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 3 --- .../Bookmarks/DeleteBookmarkFolderModal.tsx | 8 ++++---- 8 files changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/app/public/static/locales/en_US/translation.json b/packages/app/public/static/locales/en_US/translation.json index e275c184e1c..3137aca79a7 100644 --- a/packages/app/public/static/locales/en_US/translation.json +++ b/packages/app/public/static/locales/en_US/translation.json @@ -540,7 +540,8 @@ "issue_share_link": "Succeeded to issue new share link", "remove_share_link": "Succeeded to remove {{count}} share links", "switch_disable_link_sharing_success": "Succeeded to update share link setting", - "failed_to_reset_password":"Failed to reset password" + "failed_to_reset_password":"Failed to reset password", + "delete_succeeded": "Succeeded to delete {{target}}" }, "template": { "modal_label": { @@ -866,5 +867,16 @@ "footer": { "bookmarks": "Bookmarks", "recently_created": "Recently Created" + }, + "bookmark_folder":{ + "bookmark_folder": "bookmark folder", + "delete_modal": { + "modal_header_label": "Delete Bookmark Folder", + "modal_body_description": "Delete this bookmark folder and its contents", + "modal_body_alert": "Deleted folder and its contents cannot be recovered", + "modal_footer_button": "Delete Folder" + }, + "input_placeholder": "Input folder name", + "new_folder": "New Folder" } } diff --git a/packages/app/public/static/locales/ja_JP/translation.json b/packages/app/public/static/locales/ja_JP/translation.json index 8ce72bccd73..e2879cf3b6e 100644 --- a/packages/app/public/static/locales/ja_JP/translation.json +++ b/packages/app/public/static/locales/ja_JP/translation.json @@ -531,7 +531,8 @@ "issue_share_link": "ć…±æœ‰ăƒȘăƒłă‚Żă‚’äœœæˆă—ăŸă—ăŸ", "remove_share_link": "ć…±æœ‰ăƒȘンクを{{count}}ä»¶ć‰Šé™€ă—ăŸă—ăŸ", "switch_disable_link_sharing_success": "ć…±æœ‰ăƒȘăƒłă‚Żăźèš­ćźšă‚’ć€‰æ›Žă—ăŸă—ăŸ", - "failed_to_reset_password":"パă‚čăƒŻăƒŒăƒ‰ăźăƒȘă‚»ăƒƒăƒˆă«ć€±æ•—ă—ăŸă—ăŸ" + "failed_to_reset_password":"パă‚čăƒŻăƒŒăƒ‰ăźăƒȘă‚»ăƒƒăƒˆă«ć€±æ•—ă—ăŸă—ăŸ", + "delete_succeeded": "{{target}}ăźć‰Šé™€ă«æˆćŠŸă—ăŸă—ăŸ" }, "template": { "modal_label": { @@ -857,5 +858,16 @@ "footer": { "bookmarks": "ăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Ż", "recently_created": "æœ€èż‘äœœæˆă—ăŸăƒšăƒŒă‚ž" + }, + "bookmark_folder":{ + "bookmark_folder": "ăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Ż ăƒ•ă‚©ăƒ«ăƒ€", + "delete_modal": { + "modal_header_label": "ăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Żăƒ•ă‚©ăƒ«ăƒ€ă‚’ć‰Šé™€", + "modal_body_description": "ă“ăźăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Ż ăƒ•ă‚©ăƒ«ăƒ€ăšăăźć†…ćźčを扊陀する", + "modal_body_alert": "ć‰Šé™€ă•ă‚ŒăŸăƒ•ă‚©ăƒ«ăƒ€ăšăăźć†…ćźčăŻćŸ©ć…ƒă§ăăŸă›ă‚“", + "modal_footer_button": "ăƒ•ă‚©ăƒ«ăƒ€ă‚’ć‰Šé™€" + }, + "input_placeholder": "ć…„ćŠ›ăƒ•ă‚©ăƒ«ăƒ€ć", + "new_folder": "æ–°ă—ă„ăƒ•ă‚©ăƒ«ăƒ€" } } diff --git a/packages/app/public/static/locales/zh_CN/translation.json b/packages/app/public/static/locales/zh_CN/translation.json index d6e1ed85d09..f2270950d50 100644 --- a/packages/app/public/static/locales/zh_CN/translation.json +++ b/packages/app/public/static/locales/zh_CN/translation.json @@ -510,7 +510,8 @@ "remove_user_success": "Succeeded to removing {{username}} ", "remove_external_user_success": "Succeeded to remove {{accountId}} ", "switch_disable_link_sharing_success": "æˆćŠŸæ›Žæ–°ćˆ†äș«é“ŸæŽ„èźŸçœź", - "failed_to_reset_password":"Failed to reset password" + "failed_to_reset_password":"Failed to reset password", + "delete_succeeded": "Succeeded to delete {{target}}" }, "template": { "modal_label": { @@ -913,5 +914,16 @@ "footer": { "bookmarks": "äčŠç­Ÿ", "recently_created": "æœ€èż‘ćˆ›ć»șéĄ”éą" + }, + "bookmark_folder":{ + "bookmark_folder": "äčŠç­Ÿæ–‡ä»¶ć€č", + "delete_modal": { + "modal_header_label": "ćˆ é™€äčŠç­Ÿæ–‡ä»¶ć€č", + "modal_body_description": "ćˆ é™€æ­€äčŠç­Ÿæ–‡ä»¶ć€čćŠć…¶ć†…ćźč", + "modal_body_alert": "ć·Čćˆ é™€çš„æ–‡ä»¶ć€čćŠć…¶ć†…ćźčæ— æł•æąć€", + "modal_footer_button": "ćˆ é™€æ–‡ä»¶ć€č" + }, + "input_placeholder": "èŸ“ć…„æ–‡ä»¶ć€č損称", + "new_folder": "新ć»ș文件ć€č" } } diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx index 0bb183643a0..5c22cabb796 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx @@ -28,7 +28,7 @@ const BookmarkFolder = (): JSX.Element => { await apiv3Post('/bookmark-folder', { name: folderName, parent: null }); await mutateChildBookmarkData(); setIsRenameInputShown(false); - toastSuccess(t('Create New Bookmark Folder Success')); + toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder') })); } catch (err) { toastError(err); @@ -44,7 +44,7 @@ const BookmarkFolder = (): JSX.Element => { onClick={onClickBookmarkFolder} > - New Folder + {t('bookmark_folder.new_folder')}
          { diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 314a2b2024f..0201f9c019a 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -72,17 +72,19 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt const onPressEnterHandler = useCallback(async(folderName: string) => { try { if (isRenameAction) { + // Rename bookmark folder await apiv3Put('/bookmark-folder', { bookmarkFolderId: folderId, name: folderName, parent }); loadParent(); setIsRenameAction(false); - toastSuccess(t('Rename Bookmark Folder Success')); + toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder') })); } else { + // Create new bookmark folder / subfolder await apiv3Post('/bookmark-folder', { name: folderName, parent: targetFolder }); setIsOpen(true); setIsRenameInputShown(false); mutateChildBookmarkData(); - toastSuccess(t('Create New Bookmark Folder Success')); + toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder') })); } } catch (err) { @@ -128,7 +130,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt await apiv3Delete(`/bookmark-folder/${folderId}`); setIsDeleteFolderModalShown(false); loadParent(); - toastSuccess(t('Delete bookmark folder success')); + toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder') })); } catch (err) { diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx index 2abd0b5dac2..3314f9e9e67 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx @@ -30,7 +30,7 @@ const BookmarkFolderNameInput = (props: Props): JSX.Element => {
          { - const { t } = useTranslation(); const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(); if (bookmarkFolderData != null) { return ( diff --git a/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx b/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx index dec1e963f9d..a72f58698eb 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx @@ -26,14 +26,14 @@ const DeleteBookmarkFolderModal = (props: DeleteBookmarkFolderModalProps): JSX.E - {t('Delete Bookmark Folder')} + {t('bookmark_folder.delete_modal.modal_header_label')}
          -
          +
          {bookmarkFolder.name}
          - {t('Deleted folder and its contents cannot be recovered')} + {t('bookmark_folder.delete_modal.modal_body_alert')}
          From 9d678ac1cdd6d36cbd6947c5b860bcf1f243bb1a Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 16 Nov 2022 18:59:23 +0800 Subject: [PATCH 073/826] Populate bookmarked page from bookmark folder https://youtrack.weseek.co.jp/issue/GW-7861 - Add folder to Bookmark schema reference to BookmarkFolder - Add virtual bookmarks to bookmarkFolderSchema - Populate bookmarks in findFolderAndChildren method - Add bookmarks to BookmarkFolderItems interface - Update return value of bookmark folder list route --- packages/app/src/interfaces/bookmark-info.ts | 1 + packages/app/src/server/models/bookmark-folder.ts | 14 +++++++++++--- packages/app/src/server/models/bookmark.js | 1 + .../app/src/server/routes/apiv3/bookmark-folder.ts | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/app/src/interfaces/bookmark-info.ts b/packages/app/src/interfaces/bookmark-info.ts index fa46635bd43..7d2b5e5bcb2 100644 --- a/packages/app/src/interfaces/bookmark-info.ts +++ b/packages/app/src/interfaces/bookmark-info.ts @@ -29,4 +29,5 @@ export interface BookmarkFolderItems { name: string parent: string children: this[] + bookmarks: BookmarkedPage[] } diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index c538f7f8891..d9c61bf81bc 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -40,6 +40,11 @@ bookmarkFolderSchema.virtual('children', { foreignField: 'parent', }); +bookmarkFolderSchema.virtual('bookmarks', { + ref: 'Bookmark', + localField: '_id', + foreignField: 'folder', +}); bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolder): Promise { const { name, owner, parent } = params; @@ -69,10 +74,13 @@ bookmarkFolderSchema.statics.findFolderAndChildren = async function( userId: Types.ObjectId | string, parentId?: Types.ObjectId | string, ): Promise { - const parentFolder = await this.findById(parentId) as unknown as BookmarkFolderDocument; - const bookmarks = await this.find({ owner: userId, parent: parentFolder }) + + const parentFolder = await this.findById(parentId).populate({ path: 'bookmarks' }).exec() as unknown as BookmarkFolderDocument; + + // TODO : forms the return value for the bookmark + const bookmarkFolders = await this.find({ owner: userId, parent: parentFolder }) .populate({ path: 'children' }).exec() as unknown as BookmarkFolderItems[]; - return bookmarks; + return bookmarkFolders; }; bookmarkFolderSchema.statics.findChildFolderById = async function(parentFolderId: Types.ObjectId | string): Promise { diff --git a/packages/app/src/server/models/bookmark.js b/packages/app/src/server/models/bookmark.js index 4c36f45641f..aa7fae3cb3d 100644 --- a/packages/app/src/server/models/bookmark.js +++ b/packages/app/src/server/models/bookmark.js @@ -16,6 +16,7 @@ module.exports = function(crowi) { bookmarkSchema = new mongoose.Schema({ page: { type: ObjectId, ref: 'Page', index: true }, user: { type: ObjectId, ref: 'User', index: true }, + folder: { type: ObjectId, ref: 'BookmarkFolder', required: false }, }, { timestamps: { createdAt: true, updatedAt: false }, }); diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index f3138f99beb..fdffd5bdb88 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -56,6 +56,7 @@ module.exports = (crowi) => { name: bookmarkFolder.name, parent: bookmarkFolder.parent, children: bookmarkFolder.children, + bookmarks: bookmarkFolder.bookmarks, })); return res.apiv3({ bookmarkFolderItems }); } From b5566f67bd8e211564e236be4a9d6d1be3b02859 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 17 Nov 2022 18:39:49 +0800 Subject: [PATCH 074/826] Move bookmarks item component https://youtrack.weseek.co.jp/issue/GW-7861 - Remove rendering bookmark items from Bookmarks component - Move BookmarkItem component implementation to BookmarkFolderTree - Fix tooltip text of bookmark item - Adjust styling of bookmark item - Adjust Bookmark folder display for guest user - Adjust render bookmark item when no bookmarked pages - Remove unused definition of virtual bookmarks - Remove folder from bookmark Schema - Update return value of bookmark folder list route --- .../app/src/components/Sidebar/Bookmarks.tsx | 69 ++---------------- .../Bookmarks/BookmarkFolderTree.module.scss | 4 + .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 73 ++++++++++++++++--- .../Sidebar/Bookmarks/BookmarkItem.tsx | 8 +- packages/app/src/interfaces/bookmark-info.ts | 1 - .../app/src/server/models/bookmark-folder.ts | 13 +--- packages/app/src/server/models/bookmark.js | 1 - .../server/routes/apiv3/bookmark-folder.ts | 1 - 8 files changed, 77 insertions(+), 93 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 6dc59bfcdb3..5ead6fcc86f 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -3,88 +3,29 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { toastSuccess } from '~/client/util/apiNotification'; -import { IPageToDeleteWithMeta } from '~/interfaces/page'; -import { OnDeletedFunction } from '~/interfaces/ui'; -import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useIsGuestUser } from '~/stores/context'; -import { usePageDeleteModal } from '~/stores/modal'; - import BookmarkFolder from './Bookmarks/BookmarkFolder'; -import BookmarkItem from './Bookmarks/BookmarkItem'; const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); const { data: isGuestUser } = useIsGuestUser(); - const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); - const { open: openDeleteModal } = usePageDeleteModal(); - - - const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { - const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { - if (typeof pathOrPathsToDelete !== 'string') { - return; - } - const path = pathOrPathsToDelete; - - if (isCompletely) { - toastSuccess(t('deleted_pages_completely', { path })); - } - else { - toastSuccess(t('deleted_pages', { path })); - } - mutateCurrentUserBookmarks(); - }; - openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); - }; - - - const renderBookmarkList = () => { - if (currentUserBookmarksData?.length === 0) { - return ( -

          - { t('No bookmarks yet') } -

          - ); - } - return ( -
            -
            - { currentUserBookmarksData?.map((currentUserBookmark) => { - return ( - - ); - })} -
            -
          - ); - }; return ( <>

          {t('Bookmarks')}

          - {!isGuestUser && ( + {!isGuestUser ? ( <> + ) : ( +

          + { t('Not available for guest') } +

          ) } - { isGuestUser - ? ( -

          - { t('Not available for guest') } -

          - ) : renderBookmarkList() - } ); }; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss index d0312096bdb..74219259934 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss @@ -58,6 +58,10 @@ $grw-foldertree-item-padding-left: 10px; min-width: 35px; height: 40px; } + .bookmark-item-list{ + min-width: 30px; + height: 35px; + } } } &:global{ diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx index 08790e5e069..f593a7ca5cb 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -1,27 +1,76 @@ +import { useTranslation } from 'next-i18next'; + +import { toastSuccess } from '~/client/util/apiNotification'; +import { IPageToDeleteWithMeta } from '~/interfaces/page'; +import { OnDeletedFunction } from '~/interfaces/ui'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; +import { usePageDeleteModal } from '~/stores/modal'; import BookmarkFolderItem from './BookmarkFolderItem'; +import BookmarkItem from './BookmarkItem'; import styles from './BookmarkFolderTree.module.scss'; const BookmarkFolderTree = (): JSX.Element => { + const { t } = useTranslation(); + const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(); + const { open: openDeleteModal } = usePageDeleteModal(); + + const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { + const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + if (typeof pathOrPathsToDelete !== 'string') { + return; + } + const path = pathOrPathsToDelete; + + if (isCompletely) { + toastSuccess(t('deleted_pages_completely', { path })); + } + else { + toastSuccess(t('deleted_pages', { path })); + } + mutateCurrentUserBookmarks(); + }; + openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); + }; + if (bookmarkFolderData != null) { return ( - -
            - {bookmarkFolderData.map((item) => { - return ( - - ); - })} -
          + <> +
            + {bookmarkFolderData.map((item) => { + return ( + + ); + })} + {currentUserBookmarksData?.length === 0 && ( +
            +
            + { t('No bookmarks yet') } +
            +
            + )} + { currentUserBookmarksData?.map((currentUserBookmark) => { + return ( + + ); + })} +
          + ); } return <>; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx index b070ee9b4b6..f9ec116988e 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx @@ -96,8 +96,8 @@ const BookmarkItem = (props: Props): JSX.Element => { }, [bookmarkedPage, onClickDeleteMenuItem]); return ( -
          -
        • +
          +
        • { isRenameInputShown ? ( { inputValidator={inputValidator} /> ) : ( - +

          {pageTitle}

          )} @@ -130,7 +130,7 @@ const BookmarkItem = (props: Props): JSX.Element => { target={bookmarkItemId} fade={false} > - { formerPagePath } + { formerPagePath !== null ? `${formerPagePath}/` : '/' }
        • diff --git a/packages/app/src/interfaces/bookmark-info.ts b/packages/app/src/interfaces/bookmark-info.ts index 7d2b5e5bcb2..fa46635bd43 100644 --- a/packages/app/src/interfaces/bookmark-info.ts +++ b/packages/app/src/interfaces/bookmark-info.ts @@ -29,5 +29,4 @@ export interface BookmarkFolderItems { name: string parent: string children: this[] - bookmarks: BookmarkedPage[] } diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index d9c61bf81bc..464d1462883 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -32,6 +32,8 @@ const bookmarkFolderSchema = new Schema { const { name, owner, parent } = params; let bookmarkFolder; @@ -75,9 +71,7 @@ bookmarkFolderSchema.statics.findFolderAndChildren = async function( parentId?: Types.ObjectId | string, ): Promise { - const parentFolder = await this.findById(parentId).populate({ path: 'bookmarks' }).exec() as unknown as BookmarkFolderDocument; - - // TODO : forms the return value for the bookmark + const parentFolder = await this.findById(parentId) as unknown as BookmarkFolderDocument; const bookmarkFolders = await this.find({ owner: userId, parent: parentFolder }) .populate({ path: 'children' }).exec() as unknown as BookmarkFolderItems[]; return bookmarkFolders; @@ -112,6 +106,5 @@ bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolde }; -bookmarkFolderSchema.set('toObject', { virtuals: true }); export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema); diff --git a/packages/app/src/server/models/bookmark.js b/packages/app/src/server/models/bookmark.js index aa7fae3cb3d..4c36f45641f 100644 --- a/packages/app/src/server/models/bookmark.js +++ b/packages/app/src/server/models/bookmark.js @@ -16,7 +16,6 @@ module.exports = function(crowi) { bookmarkSchema = new mongoose.Schema({ page: { type: ObjectId, ref: 'Page', index: true }, user: { type: ObjectId, ref: 'User', index: true }, - folder: { type: ObjectId, ref: 'BookmarkFolder', required: false }, }, { timestamps: { createdAt: true, updatedAt: false }, }); diff --git a/packages/app/src/server/routes/apiv3/bookmark-folder.ts b/packages/app/src/server/routes/apiv3/bookmark-folder.ts index fdffd5bdb88..f3138f99beb 100644 --- a/packages/app/src/server/routes/apiv3/bookmark-folder.ts +++ b/packages/app/src/server/routes/apiv3/bookmark-folder.ts @@ -56,7 +56,6 @@ module.exports = (crowi) => { name: bookmarkFolder.name, parent: bookmarkFolder.parent, children: bookmarkFolder.children, - bookmarks: bookmarkFolder.bookmarks, })); return res.apiv3({ bookmarkFolderItems }); } From eb53228f8523b76d17eed5f5f17e9ff4377cca70 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 18 Nov 2022 11:40:28 +0800 Subject: [PATCH 075/826] Update and adjust component https://youtrack.weseek.co.jp/issue/GW-7861 - Rename BookmarkFolder to BookmarkContents component and adjust import - Update conditional rendering of BookmarkContents - Implement useCallback to deleteMenuItemClickHandler method - Add rendering function for add new bookmark button and bookmark folder name input --- packages/app/src/components/Sidebar/Bookmarks.tsx | 13 +++++-------- .../{BookmarkFolder.tsx => BookmarkContents.tsx} | 14 +++++++++++--- .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 6 ++++-- 3 files changed, 20 insertions(+), 13 deletions(-) rename packages/app/src/components/Sidebar/Bookmarks/{BookmarkFolder.tsx => BookmarkContents.tsx} (90%) diff --git a/packages/app/src/components/Sidebar/Bookmarks.tsx b/packages/app/src/components/Sidebar/Bookmarks.tsx index 5ead6fcc86f..71ddca787bf 100644 --- a/packages/app/src/components/Sidebar/Bookmarks.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { useIsGuestUser } from '~/stores/context'; -import BookmarkFolder from './Bookmarks/BookmarkFolder'; +import BookamrkContents from './Bookmarks/BookmarkContents'; const Bookmarks = () : JSX.Element => { const { t } = useTranslation(); @@ -16,16 +16,13 @@ const Bookmarks = () : JSX.Element => {

          {t('Bookmarks')}

          - {!isGuestUser ? ( - <> - - - ) : ( + {isGuestUser ? (

          { t('Not available for guest') }

          - ) - } + ) : ( + + )} ); }; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx similarity index 90% rename from packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx rename to packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx index 5c22cabb796..977a026c324 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolder.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx @@ -11,7 +11,7 @@ import BookmarkFolderNameInput from './BookmarkFolderNameInput'; import BookmarkFolderTree from './BookmarkFolderTree'; -const BookmarkFolder = (): JSX.Element => { +const BookmarkContents = (): JSX.Element => { const { t } = useTranslation(); const [isRenameInputShown, setIsRenameInputShown] = useState(false); @@ -36,7 +36,7 @@ const BookmarkFolder = (): JSX.Element => { }, [mutateChildBookmarkData, t]); - return ( + const renderAddNewBookmarkFolder = () => ( <>
          ) } + + ); + + return ( + <> + { + renderAddNewBookmarkFolder() + } ); }; -export default BookmarkFolder; +export default BookmarkContents; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx index f593a7ca5cb..02b1655bf34 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -1,4 +1,6 @@ +import { useCallback } from 'react'; + import { useTranslation } from 'next-i18next'; import { toastSuccess } from '~/client/util/apiNotification'; @@ -20,7 +22,7 @@ const BookmarkFolderTree = (): JSX.Element => { const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(); const { open: openDeleteModal } = usePageDeleteModal(); - const deleteMenuItemClickHandler = (pageToDelete: IPageToDeleteWithMeta) => { + const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => { const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { if (typeof pathOrPathsToDelete !== 'string') { return; @@ -36,7 +38,7 @@ const BookmarkFolderTree = (): JSX.Element => { mutateCurrentUserBookmarks(); }; openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); - }; + }, [mutateCurrentUserBookmarks, openDeleteModal, t]); if (bookmarkFolderData != null) { return ( From 3dbea9a15317614581430dd10aa8752e1ef40b20 Mon Sep 17 00:00:00 2001 From: Mudana-Grune <92426728+mudana-grune@users.noreply.github.com> Date: Fri, 18 Nov 2022 16:04:39 +0800 Subject: [PATCH 076/826] Update packages/app/public/static/locales/ja_JP/translation.json Co-authored-by: Kaori Tokashiki <59536731+kaoritokashiki@users.noreply.github.com> --- packages/app/public/static/locales/ja_JP/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/public/static/locales/ja_JP/translation.json b/packages/app/public/static/locales/ja_JP/translation.json index e2879cf3b6e..dbd6c3e6d37 100644 --- a/packages/app/public/static/locales/ja_JP/translation.json +++ b/packages/app/public/static/locales/ja_JP/translation.json @@ -532,7 +532,7 @@ "remove_share_link": "ć…±æœ‰ăƒȘンクを{{count}}ä»¶ć‰Šé™€ă—ăŸă—ăŸ", "switch_disable_link_sharing_success": "ć…±æœ‰ăƒȘăƒłă‚Żăźèš­ćźšă‚’ć€‰æ›Žă—ăŸă—ăŸ", "failed_to_reset_password":"パă‚čăƒŻăƒŒăƒ‰ăźăƒȘă‚»ăƒƒăƒˆă«ć€±æ•—ă—ăŸă—ăŸ", - "delete_succeeded": "{{target}}ăźć‰Šé™€ă«æˆćŠŸă—ăŸă—ăŸ" + "delete_succeeded": "{{target}} ăźć‰Šé™€ă«æˆćŠŸă—ăŸă—ăŸ" }, "template": { "modal_label": { From 2e773e31448bacaabd8416b1ac4d2c1948f35da4 Mon Sep 17 00:00:00 2001 From: Mudana-Grune <92426728+mudana-grune@users.noreply.github.com> Date: Fri, 18 Nov 2022 16:04:48 +0800 Subject: [PATCH 077/826] Update packages/app/public/static/locales/ja_JP/translation.json Co-authored-by: Kaori Tokashiki <59536731+kaoritokashiki@users.noreply.github.com> --- packages/app/public/static/locales/ja_JP/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/public/static/locales/ja_JP/translation.json b/packages/app/public/static/locales/ja_JP/translation.json index dbd6c3e6d37..d06914a8bfd 100644 --- a/packages/app/public/static/locales/ja_JP/translation.json +++ b/packages/app/public/static/locales/ja_JP/translation.json @@ -867,7 +867,7 @@ "modal_body_alert": "ć‰Šé™€ă•ă‚ŒăŸăƒ•ă‚©ăƒ«ăƒ€ăšăăźć†…ćźčăŻćŸ©ć…ƒă§ăăŸă›ă‚“", "modal_footer_button": "ăƒ•ă‚©ăƒ«ăƒ€ă‚’ć‰Šé™€" }, - "input_placeholder": "ć…„ćŠ›ăƒ•ă‚©ăƒ«ăƒ€ć", + "input_placeholder": "ăƒ•ă‚©ăƒ«ăƒ€ćă‚’ć…„ćŠ›ă—ăŠăă ă•ă„`", "new_folder": "æ–°ă—ă„ăƒ•ă‚©ăƒ«ăƒ€" } } From ff14212304f907e15d0d035949ae48220de695bd Mon Sep 17 00:00:00 2001 From: Mudana-Grune <92426728+mudana-grune@users.noreply.github.com> Date: Fri, 18 Nov 2022 16:05:00 +0800 Subject: [PATCH 078/826] Update packages/app/public/static/locales/ja_JP/translation.json Co-authored-by: Kaori Tokashiki <59536731+kaoritokashiki@users.noreply.github.com> --- packages/app/public/static/locales/ja_JP/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/public/static/locales/ja_JP/translation.json b/packages/app/public/static/locales/ja_JP/translation.json index d06914a8bfd..99abb5de978 100644 --- a/packages/app/public/static/locales/ja_JP/translation.json +++ b/packages/app/public/static/locales/ja_JP/translation.json @@ -860,7 +860,7 @@ "recently_created": "æœ€èż‘äœœæˆă—ăŸăƒšăƒŒă‚ž" }, "bookmark_folder":{ - "bookmark_folder": "ăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Ż ăƒ•ă‚©ăƒ«ăƒ€", + "bookmark_folder": "ăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Żăƒ•ă‚©ăƒ«ăƒ€", "delete_modal": { "modal_header_label": "ăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Żăƒ•ă‚©ăƒ«ăƒ€ă‚’ć‰Šé™€", "modal_body_description": "ă“ăźăƒ–ăƒƒă‚ŻăƒžăƒŒă‚Ż ăƒ•ă‚©ăƒ«ăƒ€ăšăăźć†…ćźčを扊陀する", From 903bd61539ad8f55dc1dc07576dded0143664cb9 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 18 Nov 2022 18:48:19 +0800 Subject: [PATCH 079/826] Distinguish rename and create new folder method https://youtrack.weseek.co.jp/issue/GW-7905 - Remove isRenameInputShown state - Add isCreateAction state - Modify onPressEnterHandler to onPressEnterHandlerForRename and create onPressEnterHandlerForCreate method - Adjust implementation of rename and create new folder handler - Update condition to render bookmark folder and bookmark item - Remove isOpen state from useEffect (isOpen causes improper data fetching) --- .../Sidebar/Bookmarks/BookmarkContents.tsx | 18 ++--- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 58 ++++++++------- .../Sidebar/Bookmarks/BookmarkFolderTree.tsx | 71 +++++++++---------- 3 files changed, 75 insertions(+), 72 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx index 977a026c324..891dce8e7d3 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx @@ -14,20 +14,20 @@ import BookmarkFolderTree from './BookmarkFolderTree'; const BookmarkContents = (): JSX.Element => { const { t } = useTranslation(); - const [isRenameInputShown, setIsRenameInputShown] = useState(false); + const [isCreateAction, setIsCreateAction] = useState(false); const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); - const onClickBookmarkFolder = () => { - setIsRenameInputShown(true); + const onClickNewBookmarkFolder = () => { + setIsCreateAction(true); }; - const onPressEnterHandler = useCallback(async(folderName: string) => { + const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { try { await apiv3Post('/bookmark-folder', { name: folderName, parent: null }); await mutateChildBookmarkData(); - setIsRenameInputShown(false); + setIsCreateAction(false); toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder') })); } catch (err) { @@ -41,18 +41,18 @@ const BookmarkContents = (): JSX.Element => {
          { - isRenameInputShown && ( + isCreateAction && (
          setIsRenameInputShown(false)} - onPressEnter={onPressEnterHandler} + onClickOutside={() => setIsCreateAction(false)} + onPressEnter={onPressEnterHandlerForCreate} />
          ) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 0201f9c019a..f94ca94c124 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -30,12 +30,12 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt name, _id: folderId, children, parent, } = bookmarkFolder; const [currentChildren, setCurrentChildren] = useState(); - const [isRenameInputShown, setIsRenameInputShown] = useState(false); const [targetFolder, setTargetFolder] = useState(folderId); const [isOpen, setIsOpen] = useState(_isOpen); const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(targetFolder); const { mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild(parent); const [isRenameAction, setIsRenameAction] = useState(false); + const [isCreateAction, setIsCreateAction] = useState(false); const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState(false); const childCount = useCallback((): number => { @@ -46,12 +46,11 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt }, [children.length, currentChildren]); useEffect(() => { - if (isOpen) { + if (childBookmarkFolderData != null) { mutateChildBookmarkData(); setCurrentChildren(childBookmarkFolderData); } - }, [childBookmarkFolderData, isOpen, mutateChildBookmarkData]); - + }, [childBookmarkFolderData, mutateChildBookmarkData]); const hasChildren = useCallback((): boolean => { if (currentChildren != null && currentChildren.length > children.length) { @@ -69,35 +68,40 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt mutateParentBookmarkFolder(); }, [mutateParentBookmarkFolder]); - const onPressEnterHandler = useCallback(async(folderName: string) => { + // Rename for bookmark folder handler + const onPressEnterHandlerForRename = useCallback(async(folderName: string) => { try { - if (isRenameAction) { - // Rename bookmark folder - await apiv3Put('/bookmark-folder', { bookmarkFolderId: folderId, name: folderName, parent }); - loadParent(); - setIsRenameAction(false); - toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder') })); - } - else { - // Create new bookmark folder / subfolder - await apiv3Post('/bookmark-folder', { name: folderName, parent: targetFolder }); - setIsOpen(true); - setIsRenameInputShown(false); - mutateChildBookmarkData(); - toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder') })); - } + await apiv3Put('/bookmark-folder', { bookmarkFolderId: folderId, name: folderName, parent }); + loadParent(); + setIsRenameAction(false); + toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder') })); + } + catch (err) { + toastError(err); + } + }, [folderId, loadParent, parent, t]); + + // Create new folder / subfolder handler + const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { + try { + await apiv3Post('/bookmark-folder', { name: folderName, parent: targetFolder }); + setIsOpen(true); + setIsCreateAction(false); + mutateChildBookmarkData(); + toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder') })); + } catch (err) { toastError(err); } - }, [folderId, isRenameAction, loadParent, mutateChildBookmarkData, parent, t, targetFolder]); + }, [mutateChildBookmarkData, t, targetFolder]); const onClickPlusButton = useCallback(async() => { if (!isOpen && hasChildren()) { setIsOpen(true); } - setIsRenameInputShown(true); + setIsCreateAction(true); }, [hasChildren, isOpen]); const RenderChildFolder = () => { @@ -131,7 +135,6 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt setIsDeleteFolderModalShown(false); loadParent(); toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder') })); - } catch (err) { toastError(err); @@ -163,7 +166,8 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt { isRenameAction ? ( setIsRenameAction(false)} - onPressEnter={onPressEnterHandler} value={name} + onPressEnter={onPressEnterHandlerForRename} + value={name} /> ) : ( <> @@ -199,11 +203,11 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt
        - {isRenameInputShown && ( + {isCreateAction && (
        setIsRenameInputShown(false)} - onPressEnter={onPressEnterHandler} + onClickOutside={() => setIsCreateAction(false)} + onPressEnter={onPressEnterHandlerForCreate} />
        )} diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx index 02b1655bf34..24149f0c93c 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx @@ -40,42 +40,41 @@ const BookmarkFolderTree = (): JSX.Element => { openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); }, [mutateCurrentUserBookmarks, openDeleteModal, t]); - if (bookmarkFolderData != null) { - return ( - <> -
          - {bookmarkFolderData.map((item) => { - return ( - - ); - })} - {currentUserBookmarksData?.length === 0 && ( -
          -
          - { t('No bookmarks yet') } -
          -
          - )} - { currentUserBookmarksData?.map((currentUserBookmark) => { - return ( - - ); - })} -
        - - ); - } - return <>; + + return ( + <> +
          + {bookmarkFolderData?.map((item) => { + return ( + + ); + })} + {currentUserBookmarksData?.length === 0 && ( +
          +
          + { t('No bookmarks yet') } +
          +
          + )} + { currentUserBookmarksData?.map((currentUserBookmark) => { + return ( + + ); + })} +
        + + ); + }; From 70436f88ab4ddd56961815d96d81c48dd51cfc47 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 22 Nov 2022 19:22:16 +0800 Subject: [PATCH 080/826] Fix child count on delete https://youtrack.weseek.co.jp/issue/GW-7905 - Move onClickDeleteButtonHandler method - Reload bookmark data from BookmarkContents componet on delete - Change condition to load parent - Load parent by setTargetFolder to null --- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index f94ca94c124..2aa909368ac 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -65,8 +65,12 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt }, [folderId, isOpen]); const loadParent = useCallback(async() => { - mutateParentBookmarkFolder(); - }, [mutateParentBookmarkFolder]); + if (parent != null) { + mutateParentBookmarkFolder(); + } + setTargetFolder(null); + + }, [mutateParentBookmarkFolder, parent]); // Rename for bookmark folder handler const onPressEnterHandlerForRename = useCallback(async(folderName: string) => { @@ -97,6 +101,19 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt }, [mutateChildBookmarkData, t, targetFolder]); + // Delete Fodler handler + const onClickDeleteButtonHandler = useCallback(async() => { + try { + await apiv3Delete(`/bookmark-folder/${folderId}`); + setIsDeleteFolderModalShown(false); + loadParent(); + toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder') })); + } + catch (err) { + toastError(err); + } + }, [folderId, loadParent, t]); + const onClickPlusButton = useCallback(async() => { if (!isOpen && hasChildren()) { setIsOpen(true); @@ -129,17 +146,6 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt setIsDeleteFolderModalShown(false); }, []); - const onClickDeleteButtonHandler = useCallback(async() => { - try { - await apiv3Delete(`/bookmark-folder/${folderId}`); - setIsDeleteFolderModalShown(false); - loadParent(); - toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder') })); - } - catch (err) { - toastError(err); - } - }, [folderId, loadParent, t]); return (
        Date: Wed, 23 Nov 2022 19:13:26 +0800 Subject: [PATCH 081/826] Fix child count and recursive deletion https://youtrack.weseek.co.jp/issue/GW-7905 - Update condition of load parent for rename and delete - Update condition of delete root folder - Update deleteFolderAndChildren method - Update bookmarkFoldeId type parameter --- .../Sidebar/Bookmarks/BookmarkFolderItem.tsx | 14 ++++++++++---- packages/app/src/server/models/bookmark-folder.ts | 12 +++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 2aa909368ac..1ff4c56cfc1 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -65,12 +65,18 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt }, [folderId, isOpen]); const loadParent = useCallback(async() => { - if (parent != null) { - mutateParentBookmarkFolder(); + if (!isRenameAction) { + if (parent != null) { + await mutateParentBookmarkFolder(); + } + // Reload root folder structure + setTargetFolder(null); + } + else { + await mutateParentBookmarkFolder(); } - setTargetFolder(null); - }, [mutateParentBookmarkFolder, parent]); + }, [isRenameAction, mutateParentBookmarkFolder, parent]); // Rename for bookmark folder handler const onPressEnterHandlerForRename = useCallback(async(folderName: string) => { diff --git a/packages/app/src/server/models/bookmark-folder.ts b/packages/app/src/server/models/bookmark-folder.ts index 464d1462883..204d859c755 100644 --- a/packages/app/src/server/models/bookmark-folder.ts +++ b/packages/app/src/server/models/bookmark-folder.ts @@ -24,7 +24,7 @@ export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolder): BookmarkFolderDocument findFolderAndChildren(user: Types.ObjectId | string, parentId?: Types.ObjectId | string): BookmarkFolderItems[] findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise - deleteFolderAndChildren(bookmarkFolderId: string): {deletedCount: number} + deleteFolderAndChildren(bookmarkFolderId: Types.ObjectId | string): {deletedCount: number} updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null } @@ -88,8 +88,14 @@ bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(boookmarkF const bookmarkFolder = await this.findByIdAndDelete(boookmarkFolderId); let deletedCount = 0; if (bookmarkFolder != null) { - const childFolders = await this.deleteMany({ parent: bookmarkFolder?.id }); - deletedCount = childFolders.deletedCount + 1; + // Delete all child recursively and update deleted count + const childFolders = await this.find({ parent: bookmarkFolder }); + await Promise.all(childFolders.map(async(child) => { + const deletedChildFolder = await this.deleteFolderAndChildren(child._id); + deletedCount += deletedChildFolder.deletedCount; + })); + const deletedChild = await this.deleteMany({ parent: bookmarkFolder }); + deletedCount += deletedChild.deletedCount + 1; } return { deletedCount }; }; From 0eb0b193c8d4928a9021ef00c5f8bef16ba019ea Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 24 Nov 2022 11:51:37 +0800 Subject: [PATCH 082/826] Implement useCallback https://youtrack.weseek.co.jp/issue/GW-7905 - Implement useCallback to onClickNewBookmarkFolder method --- .../app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx index 891dce8e7d3..d47418d7839 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx @@ -18,9 +18,9 @@ const BookmarkContents = (): JSX.Element => { const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); - const onClickNewBookmarkFolder = () => { + const onClickNewBookmarkFolder = useCallback(() => { setIsCreateAction(true); - }; + }, []); const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { From 05d14e26772d87f808bba3de6ea9bc2be0e80766 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 24 Nov 2022 12:32:44 +0800 Subject: [PATCH 083/826] Implement useCallback to renderAddNewBookmarkFolder https://youtrack.weseek.co.jp/issue/GW-7905 - Implement useCallback to renderAddNewBookmarkFolder method --- .../app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx index d47418d7839..2cdf6eeb8a8 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx @@ -36,7 +36,7 @@ const BookmarkContents = (): JSX.Element => { }, [mutateChildBookmarkData, t]); - const renderAddNewBookmarkFolder = () => ( + const renderAddNewBookmarkFolder = useCallback(() => ( <>
        +

      + { isCreateAction && ( +
      +
      + setIsCreateAction(false)} + onPressEnter={onPressEnterHandlerForCreate} + /> +
      + +
      + )}
      From 7496c29aeec2192580a0fa8c7b1120feb5b99be6 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 25 Nov 2022 18:18:37 +0800 Subject: [PATCH 085/826] Show bookmark folder and adjust bookmark list https://youtrack.weseek.co.jp/issue/GW-7849 - Show bookmark folder item in user page - Implement useSWRxCurrentUserBookmarks to BookmarkList - Adjust bookmark item listing - Remove Pagination Wrapper component - Remove unused props - Adjust implementation of BookmarList component --- .../src/components/PageList/BookmarkList.tsx | 60 +++---------------- .../src/components/UsersHomePageFooter.tsx | 18 +++++- 2 files changed, 24 insertions(+), 54 deletions(-) diff --git a/packages/app/src/components/PageList/BookmarkList.tsx b/packages/app/src/components/PageList/BookmarkList.tsx index 87dbca26f0c..c085bf49868 100644 --- a/packages/app/src/components/PageList/BookmarkList.tsx +++ b/packages/app/src/components/PageList/BookmarkList.tsx @@ -1,77 +1,33 @@ -import React, { useState, useCallback, useEffect } from 'react'; +import React from 'react'; import { useTranslation } from 'next-i18next'; -import { toastError } from '~/client/util/apiNotification'; -import { apiv3Get } from '~/client/util/apiv3-client'; -import { MyBookmarkList } from '~/interfaces/bookmark-info'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import loggerFactory from '~/utils/logger'; -import PaginationWrapper from '../PaginationWrapper'; - import { PageListItemS } from './PageListItemS'; const logger = loggerFactory('growi:BookmarkList'); -type BookmarkListProps = { - userId: string -} - -export const BookmarkList = (props: BookmarkListProps): JSX.Element => { - const { userId } = props; +export const BookmarkList = (): JSX.Element => { const { t } = useTranslation(); - const [pages, setPages] = useState([]); - const [activePage, setActivePage] = useState(1); - const [totalItemsCount, setTotalItemsCount] = useState(0); - const [pagingLimit, setPagingLimit] = useState(10); - - const setPageNumber = (selectedPageNumber) => { - setActivePage(selectedPageNumber); - }; - - const getMyBookmarkList = useCallback(async() => { - const page = activePage; - - try { - const res = await apiv3Get(`/bookmarks/${userId}`, { page }); - const { paginationResult } = res.data; - - setPages(paginationResult.docs); - setTotalItemsCount(paginationResult.totalDocs); - setPagingLimit(paginationResult.limit); - } - catch (error) { - logger.error('failed to fetch data', error); - toastError(error, 'Error occurred in bookmark page list'); - } - }, [activePage, userId]); + const { data: currentUserBookmarksData } = useSWRxCurrentUserBookmarks(); - useEffect(() => { - getMyBookmarkList(); - }, [getMyBookmarkList]); return (
      - {pages.length === 0 ? t('No bookmarks yet') : ( + {currentUserBookmarksData?.length === 0 ? t('No bookmarks yet') : ( <>
        - {pages.map(page => ( -
      • - + {currentUserBookmarksData?.map(page => ( +
      • +
      • ))}
      - )}
      diff --git a/packages/app/src/components/UsersHomePageFooter.tsx b/packages/app/src/components/UsersHomePageFooter.tsx index 7253e4ad9e6..83ae2e05e52 100644 --- a/packages/app/src/components/UsersHomePageFooter.tsx +++ b/packages/app/src/components/UsersHomePageFooter.tsx @@ -11,6 +11,7 @@ import styles from '~/components/UsersHomePageFooter.module.scss'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import FolderPlusIcon from './Icons/FolderPlusIcon'; +import BookmarkFolderItem from './Sidebar/Bookmarks/BookmarkFolderItem'; import BookmarkFolderNameInput from './Sidebar/Bookmarks/BookmarkFolderNameInput'; export type UsersHomePageFooterProps = { @@ -21,7 +22,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen const { t } = useTranslation(); const { creatorId } = props; const [isCreateAction, setIsCreateAction] = useState(false); - const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); + const { data: bookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { @@ -63,8 +64,21 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
      )} + { +
        + {bookmarkFolderData?.map((item) => { + return ( + + ); + })} +
      + }
      - +
      From 71c0bbf69c2d5d36a9386800ed4536c7475c29d7 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 25 Nov 2022 18:30:36 +0800 Subject: [PATCH 086/826] Rename childCount to getChildCount Rename method childCount to getChildCount --- .../src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx index 539bfc2c102..000a88fe8c5 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx @@ -38,7 +38,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt const [isCreateAction, setIsCreateAction] = useState(false); const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState(false); - const childCount = useCallback((): number => { + const getChildCount = useCallback((): number => { if (currentChildren != null && currentChildren.length > children.length) { return currentChildren.length; } @@ -188,7 +188,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt
      {hasChildren() && (
      - +
      )} From 3a753c1f9197c2151b8594d85925801c46738fd6 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Mon, 28 Nov 2022 18:26:20 +0800 Subject: [PATCH 087/826] Adjust styling and manage component https://youtrack.weseek.co.jp/issue/GW-7849 - Move bookmark component to new directory - Update bookmark component import - Adjust styling for dark and light theme - Create bookmark-folder-tree styling - Implement bookmark-folder-tree style to bookmark folder list in home page - Add styling for list group item - Add isSidebarItem props to BookmarkFolderItem component - Add conditional rendering for folder tree item control --- .../Bookmarks/BookmarkFolderItem.tsx | 70 +++++++++++++------ .../Bookmarks/BookmarkFolderItemControl.tsx | 0 .../Bookmarks/BookmarkFolderNameInput.tsx | 2 +- .../Bookmarks/BookmarkFolderTree.module.scss | 1 + .../Bookmarks/BookmarkFolderTree.tsx | 1 + .../{Sidebar => }/Bookmarks/BookmarkItem.tsx | 5 +- .../Bookmarks/DeleteBookmarkFolderModal.tsx | 0 .../Sidebar/Bookmarks/BookmarkContents.tsx | 5 +- .../UsersHomePageFooter.module.scss | 20 ++++++ .../src/components/UsersHomePageFooter.tsx | 15 ++-- .../molecules/_bookmark-folder-tree.scss} | 0 .../src/styles/theme/_apply-colors-dark.scss | 17 +++++ .../src/styles/theme/_apply-colors-light.scss | 17 +++++ 13 files changed, 119 insertions(+), 34 deletions(-) rename packages/app/src/components/{Sidebar => }/Bookmarks/BookmarkFolderItem.tsx (75%) rename packages/app/src/components/{Sidebar => }/Bookmarks/BookmarkFolderItemControl.tsx (100%) rename packages/app/src/components/{Sidebar => }/Bookmarks/BookmarkFolderNameInput.tsx (95%) create mode 100644 packages/app/src/components/Bookmarks/BookmarkFolderTree.module.scss rename packages/app/src/components/{Sidebar => }/Bookmarks/BookmarkFolderTree.tsx (98%) rename packages/app/src/components/{Sidebar => }/Bookmarks/BookmarkItem.tsx (96%) rename packages/app/src/components/{Sidebar => }/Bookmarks/DeleteBookmarkFolderModal.tsx (100%) rename packages/app/src/{components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss => styles/molecules/_bookmark-folder-tree.scss} (100%) diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx similarity index 75% rename from packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx rename to packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx index 539bfc2c102..a24f57edac2 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx @@ -21,9 +21,10 @@ import DeleteBookmarkFolderModal from './DeleteBookmarkFolderModal'; type BookmarkFolderItemProps = { bookmarkFolder: BookmarkFolderItems isOpen?: boolean + isSidebarItem: boolean } const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { - const { bookmarkFolder, isOpen: _isOpen = false } = props; + const { bookmarkFolder, isOpen: _isOpen = false, isSidebarItem } = props; const { t } = useTranslation(); const { @@ -134,6 +135,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt
      ); @@ -184,9 +186,34 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt ) : ( <>
      -

      {name}

      + {isSidebarItem ? ( +

      {name}

      + ) : ( +
      +

      {name}

      +
      +
      + + + + + + +
      +
      +
      + )}
      - {hasChildren() && ( + {hasChildren() && isSidebarItem && (
      @@ -195,24 +222,25 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt ) } -
      - - - - - - - -
      + { isSidebarItem && ( +
      + + + + + + +
      + )} {isCreateAction && ( diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItemControl.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx similarity index 100% rename from packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItemControl.tsx rename to packages/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx similarity index 95% rename from packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx rename to packages/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx index 3314f9e9e67..2b2f51394e1 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderNameInput.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx @@ -27,7 +27,7 @@ const BookmarkFolderNameInput = (props: Props): JSX.Element => { }; return ( -
      +
      { key={item._id} bookmarkFolder={item} isOpen={false} + isSidebarItem={true} /> ); })} diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx b/packages/app/src/components/Bookmarks/BookmarkItem.tsx similarity index 96% rename from packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx rename to packages/app/src/components/Bookmarks/BookmarkItem.tsx index f9ec116988e..6fc76741e65 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkItem.tsx @@ -12,9 +12,8 @@ import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Put } from '~/client/util/apiv3-client'; import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; - -import ClosableTextInput, { AlertInfo, AlertType } from '../../Common/ClosableTextInput'; -import { MenuItemType, PageItemControl } from '../../Common/Dropdown/PageItemControl'; +import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; +import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; type Props = { diff --git a/packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx b/packages/app/src/components/Bookmarks/DeleteBookmarkFolderModal.tsx similarity index 100% rename from packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx rename to packages/app/src/components/Bookmarks/DeleteBookmarkFolderModal.tsx diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx index 2cdf6eeb8a8..928b5881269 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx @@ -4,12 +4,11 @@ import { useTranslation } from 'next-i18next'; import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Post } from '~/client/util/apiv3-client'; +import BookmarkFolderNameInput from '~/components/Bookmarks/BookmarkFolderNameInput'; +import BookmarkFolderTree from '~/components/Bookmarks/BookmarkFolderTree'; import FolderPlusIcon from '~/components/Icons/FolderPlusIcon'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; -import BookmarkFolderNameInput from './BookmarkFolderNameInput'; -import BookmarkFolderTree from './BookmarkFolderTree'; - const BookmarkContents = (): JSX.Element => { diff --git a/packages/app/src/components/UsersHomePageFooter.module.scss b/packages/app/src/components/UsersHomePageFooter.module.scss index ef0673ecc87..943f7776acc 100644 --- a/packages/app/src/components/UsersHomePageFooter.module.scss +++ b/packages/app/src/components/UsersHomePageFooter.module.scss @@ -1,7 +1,27 @@ @use '~/styles/molecules/page_list'; +@use '~/styles/molecules/bookmark-folder-tree'; + .user-page-footer :global { .grw-user-page-list-m { + .list-group-item { + svg{ + width: 20px; + height: 20px; + } + + } + .grw-triangle-container{ + svg { + width: 12px; + height: 12px; + } + } + .grw-foldertree-item-container { + input { + max-width: 25%; + } + } svg { width: 35px; height: 35px; diff --git a/packages/app/src/components/UsersHomePageFooter.tsx b/packages/app/src/components/UsersHomePageFooter.tsx index 83ae2e05e52..1cabf1acf10 100644 --- a/packages/app/src/components/UsersHomePageFooter.tsx +++ b/packages/app/src/components/UsersHomePageFooter.tsx @@ -10,9 +10,10 @@ import { RecentCreated } from '~/components/RecentCreated/RecentCreated'; import styles from '~/components/UsersHomePageFooter.module.scss'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; +import BookmarkFolderItem from './Bookmarks/BookmarkFolderItem'; +import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput'; import FolderPlusIcon from './Icons/FolderPlusIcon'; -import BookmarkFolderItem from './Sidebar/Bookmarks/BookmarkFolderItem'; -import BookmarkFolderNameInput from './Sidebar/Bookmarks/BookmarkFolderNameInput'; + export type UsersHomePageFooterProps = { creatorId: string, @@ -65,21 +66,23 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
      )} { -
        +
          {bookmarkFolderData?.map((item) => { return ( ); })} +
          + +
        } -
        - -
        +

      diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss b/packages/app/src/styles/molecules/_bookmark-folder-tree.scss similarity index 100% rename from packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss rename to packages/app/src/styles/molecules/_bookmark-folder-tree.scss diff --git a/packages/app/src/styles/theme/_apply-colors-dark.scss b/packages/app/src/styles/theme/_apply-colors-dark.scss index 9e94bfe2c43..6b42197a698 100644 --- a/packages/app/src/styles/theme/_apply-colors-dark.scss +++ b/packages/app/src/styles/theme/_apply-colors-dark.scss @@ -362,6 +362,23 @@ ul.pagination { } } +.grw-user-page-list-m { + .grw-foldertree { + @include override-list-group-item-for-pagetree( + $body-color, + lighten($body-bg, 8%), + lighten($body-bg, 15%), + darken($body-color, 15%), + darken($body-color, 10%), + lighten($body-bg, 18%), + lighten($body-bg, 24%) + ); + .grw-foldertree-triangle-btn { + @include mixins.button-outline-svg-icon-variant($secondary, $gray-200); + } + } +} + .btn.btn-page-item-control { @include button-outline-variant($gray-500, $gray-500, $secondary, transparent); @include hover() { diff --git a/packages/app/src/styles/theme/_apply-colors-light.scss b/packages/app/src/styles/theme/_apply-colors-light.scss index 73ff2b0a010..968102e7f88 100644 --- a/packages/app/src/styles/theme/_apply-colors-light.scss +++ b/packages/app/src/styles/theme/_apply-colors-light.scss @@ -240,6 +240,23 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active; } } +.grw-user-page-list-m { + .grw-foldertree { + @include override-list-group-item-for-pagetree( + $body-color, + darken($body-bg, 5%), + darken($body-bg, 12%), + lighten($body-color, 10%), + lighten($body-color, 8%), + darken($body-bg, 15%), + darken($body-bg, 24%) + ); + .grw-foldertree-triangle-btn { + @include mixins.button-outline-svg-icon-variant($gray-400, $primary); + } + } +} + .btn.btn-page-item-control { @include button-outline-variant($gray-500, $primary, lighten($primary, 52%), transparent); @include hover() { From 66b3188026874da2233b7f271f98d59c12fb3de1 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Tue, 29 Nov 2022 18:41:30 +0800 Subject: [PATCH 088/826] Create component and adjust styling https://youtrack.weseek.co.jp/issue/GW-7849 - Show child count badge on bookmark folder list - Add isSidearItem props to BookmarkItem component - Add conditional rendering of BookmarkItem for sidebar and user page - Create BookmarkContents component for user page - Adjust styling of BookmarkContents component - Remove min-height from bookmark-folder-tree style - Implement BookmarkContents component to UsersHomePageFooter component - Remove unused import from UsersHomePageFooter --- .../Bookmarks/BookmarkFolderItem.tsx | 11 +- .../Bookmarks/BookmarkFolderTree.tsx | 1 + .../src/components/Bookmarks/BookmarkItem.tsx | 109 +++++++++++++----- .../UsersHomePageFooter.module.scss | 2 - .../src/components/UsersHomePageFooter.tsx | 21 +--- .../BookmarkContents.module.scss | 3 + .../UsersPageBookmarks/BookmarkContents.tsx | 77 +++++++++++++ .../molecules/_bookmark-folder-tree.scss | 3 - 8 files changed, 173 insertions(+), 54 deletions(-) create mode 100644 packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss create mode 100644 packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx diff --git a/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx index a24f57edac2..ab78c008d24 100644 --- a/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx @@ -190,7 +190,9 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt

      {name}

      ) : (
      -

      {name}

      +
      +

      {name}

      +
      = (props: BookmarkFolderIt
      +
      + {hasChildren() && ( +
      + +
      + )} +
      )}

      diff --git a/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx index 978a5da6ac1..99af6a60e1e 100644 --- a/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx @@ -69,6 +69,7 @@ const BookmarkFolderTree = (): JSX.Element => { onUnbookmarked={mutateCurrentUserBookmarks} onRenamed={mutateCurrentUserBookmarks} onClickDeleteMenuItem={deleteMenuItemClickHandler} + isSidebarItem={true} /> ); })} diff --git a/packages/app/src/components/Bookmarks/BookmarkItem.tsx b/packages/app/src/components/Bookmarks/BookmarkItem.tsx index 6fc76741e65..072c196e610 100644 --- a/packages/app/src/components/Bookmarks/BookmarkItem.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkItem.tsx @@ -14,19 +14,21 @@ import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/pa import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; +import { PageListItemS } from '../PageList/PageListItemS'; type Props = { bookmarkedPage: IPageHasId, onUnbookmarked: () => void, onRenamed: () => void, - onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void + onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void, + isSidebarItem: boolean } const BookmarkItem = (props: Props): JSX.Element => { const { t } = useTranslation(); const { - bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, + bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, isSidebarItem, } = props; const [isRenameInputShown, setRenameInputShown] = useState(false); const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true); @@ -94,9 +96,53 @@ const BookmarkItem = (props: Props): JSX.Element => { onClickDeleteMenuItem(pageToDelete); }, [bookmarkedPage, onClickDeleteMenuItem]); - return ( -
      -
    • + const renderBookmarkListForSidebar = () => { + return ( +
      +
    • + { isRenameInputShown ? ( + { setRenameInputShown(false) }} + onPressEnter={pressEnterForRenameHandler} + inputValidator={inputValidator} + /> + ) : ( + +

      {pageTitle}

      +
      + )} + + + + + + + { formerPagePath !== null ? `${formerPagePath}/` : '/' } + +
    • +
      + ); + }; + + const renderBookmarkListForUserPage = () => { + return ( +
    • { isRenameInputShown ? ( { onPressEnter={pressEnterForRenameHandler} inputValidator={inputValidator} /> + ) : ( - -

      {pageTitle}

      -
      + <> +
      + + + + + + +
      + )} - - - - - - - { formerPagePath !== null ? `${formerPagePath}/` : '/' } - +
    • -
      + ); + }; + return ( + <> + {isSidebarItem + ? renderBookmarkListForSidebar() + : renderBookmarkListForUserPage() + } + ); }; diff --git a/packages/app/src/components/UsersHomePageFooter.module.scss b/packages/app/src/components/UsersHomePageFooter.module.scss index 943f7776acc..65faf76f7b2 100644 --- a/packages/app/src/components/UsersHomePageFooter.module.scss +++ b/packages/app/src/components/UsersHomePageFooter.module.scss @@ -1,6 +1,4 @@ @use '~/styles/molecules/page_list'; -@use '~/styles/molecules/bookmark-folder-tree'; - .user-page-footer :global { .grw-user-page-list-m { diff --git a/packages/app/src/components/UsersHomePageFooter.tsx b/packages/app/src/components/UsersHomePageFooter.tsx index 1cabf1acf10..7339d486048 100644 --- a/packages/app/src/components/UsersHomePageFooter.tsx +++ b/packages/app/src/components/UsersHomePageFooter.tsx @@ -5,14 +5,13 @@ import { useTranslation } from 'next-i18next'; import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Post } from '~/client/util/apiv3-client'; import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon'; -import { BookmarkList } from '~/components/PageList/BookmarkList'; import { RecentCreated } from '~/components/RecentCreated/RecentCreated'; import styles from '~/components/UsersHomePageFooter.module.scss'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; -import BookmarkFolderItem from './Bookmarks/BookmarkFolderItem'; import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput'; import FolderPlusIcon from './Icons/FolderPlusIcon'; +import BookmarkContents from './UsersPageBookmarks/BookmarkContents'; export type UsersHomePageFooterProps = { @@ -23,7 +22,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen const { t } = useTranslation(); const { creatorId } = props; const [isCreateAction, setIsCreateAction] = useState(false); - const { data: bookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); + const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { @@ -66,21 +65,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
      )} { -
        - {bookmarkFolderData?.map((item) => { - return ( - - ); - })} -
        - -
        -
      + }
      diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss new file mode 100644 index 00000000000..961e3d1d3a8 --- /dev/null +++ b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss @@ -0,0 +1,3 @@ +@use '~/styles/molecules/bookmark-folder-tree'; +@use '~/styles/molecules/page_list'; + diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx new file mode 100644 index 00000000000..a4f076d48f6 --- /dev/null +++ b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx @@ -0,0 +1,77 @@ +import React, { useCallback } from 'react'; + +import { useTranslation } from 'next-i18next'; + +import { toastSuccess } from '~/client/util/apiNotification'; +import { IPageToDeleteWithMeta } from '~/interfaces/page'; +import { OnDeletedFunction } from '~/interfaces/ui'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; +import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; +import { usePageDeleteModal } from '~/stores/modal'; + +import BookmarkFolderItem from '../Bookmarks/BookmarkFolderItem'; +import BookmarkItem from '../Bookmarks/BookmarkItem'; + + +import styles from './BookmarkContents.module.scss'; + +const BookmarkContents = (): JSX.Element => { + const { t } = useTranslation(); + const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(null); + const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); + const { open: openDeleteModal } = usePageDeleteModal(); + + const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => { + const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + if (typeof pathOrPathsToDelete !== 'string') { + return; + } + const path = pathOrPathsToDelete; + + if (isCompletely) { + toastSuccess(t('deleted_pages_completely', { path })); + } + else { + toastSuccess(t('deleted_pages', { path })); + } + mutateCurrentUserBookmarks(); + }; + openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); + }, [mutateCurrentUserBookmarks, openDeleteModal, t]); + + return ( +
        + {bookmarkFolderData?.map((item) => { + return ( + + ); + })} +
        + {currentUserBookmarksData?.length === 0 ? t('No bookmarks yet') : ( + <> +
          + {currentUserBookmarksData?.map(page => ( + + ))} + +
        + + ) } +
        +
      + ); +}; + +export default BookmarkContents; diff --git a/packages/app/src/styles/molecules/_bookmark-folder-tree.scss b/packages/app/src/styles/molecules/_bookmark-folder-tree.scss index 74219259934..cb927aaabab 100644 --- a/packages/app/src/styles/molecules/_bookmark-folder-tree.scss +++ b/packages/app/src/styles/molecules/_bookmark-folder-tree.scss @@ -1,11 +1,8 @@ @use '~/styles/variables' as var; -$grw-sidebar-content-header-height: 58px; -$grw-sidebar-content-footer-height: 50px; $grw-foldertree-item-padding-left: 10px; .grw-foldertree { :global { - min-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); .btn-page-item-control { .icon-plus::before { From 820f1eb06ea8320a8b82f4243d00ce43cc974462 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Wed, 30 Nov 2022 19:27:35 +0800 Subject: [PATCH 089/826] Implement expand and compress bookmark content on user page https://youtrack.weseek.co.jp/issue/GW-7850 - Create component for Expand / Compress Icon - Implement Expand / Compress button to UsersHomePageFooter component - Add props isExpanded props to BookmarkContents component - Implement conditional styling / classes to BookmarkContents based on isExpanded state - Create styling for bookmark content continer - Adjust styling for expand / compress button --- .../components/Icons/ExpandCompressIcon.tsx | 43 ++++++++++++ .../UsersHomePageFooter.module.scss | 8 +++ .../src/components/UsersHomePageFooter.tsx | 14 +++- .../BookmarkContents.module.scss | 13 ++++ .../UsersPageBookmarks/BookmarkContents.tsx | 69 ++++++++++--------- .../src/styles/theme/_apply-colors-dark.scss | 7 ++ .../src/styles/theme/_apply-colors-light.scss | 7 ++ 7 files changed, 128 insertions(+), 33 deletions(-) create mode 100644 packages/app/src/components/Icons/ExpandCompressIcon.tsx diff --git a/packages/app/src/components/Icons/ExpandCompressIcon.tsx b/packages/app/src/components/Icons/ExpandCompressIcon.tsx new file mode 100644 index 00000000000..daf51b6fa14 --- /dev/null +++ b/packages/app/src/components/Icons/ExpandCompressIcon.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +type Props = { + isExpanded: boolean +} +const ExpandCompressIcon = (props: Props): JSX.Element => { + const { isExpanded } = props; + + return ( + <> + {isExpanded ? ( + + + + ) : ( + + + + ) + } + + ); + +}; + +export default ExpandCompressIcon; diff --git a/packages/app/src/components/UsersHomePageFooter.module.scss b/packages/app/src/components/UsersHomePageFooter.module.scss index 65faf76f7b2..f03fa53de8c 100644 --- a/packages/app/src/components/UsersHomePageFooter.module.scss +++ b/packages/app/src/components/UsersHomePageFooter.module.scss @@ -32,5 +32,13 @@ height: 18px; } } + .expand-compress-btn { + max-height: 40px; + svg { + width: 18px; + height: 18px; + margin-bottom: 3px; + } + } } } diff --git a/packages/app/src/components/UsersHomePageFooter.tsx b/packages/app/src/components/UsersHomePageFooter.tsx index 7339d486048..5fd2a6c7831 100644 --- a/packages/app/src/components/UsersHomePageFooter.tsx +++ b/packages/app/src/components/UsersHomePageFooter.tsx @@ -10,6 +10,7 @@ import styles from '~/components/UsersHomePageFooter.module.scss'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput'; +import ExpandCompressIcon from './Icons/ExpandCompressIcon'; import FolderPlusIcon from './Icons/FolderPlusIcon'; import BookmarkContents from './UsersPageBookmarks/BookmarkContents'; @@ -22,6 +23,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen const { t } = useTranslation(); const { creatorId } = props; const [isCreateAction, setIsCreateAction] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); @@ -40,7 +42,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen return (
      -

      +

      {t('footer.bookmarks')} @@ -52,6 +54,14 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen {t('bookmark_folder.new_folder')} + + +

      { isCreateAction && (
      @@ -65,7 +75,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
      )} { - + }
      diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss index 961e3d1d3a8..74dfe5305d4 100644 --- a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss +++ b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss @@ -1,3 +1,16 @@ @use '~/styles/molecules/bookmark-folder-tree'; @use '~/styles/molecules/page_list'; +@use '~/styles/variables' as var; +$grw-sidebar-content-header-height: 58px; +$grw-sidebar-content-footer-height: 50px; + +.compressed { + max-height: calc(70vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); + overflow-y: scroll; +} + +.expanded { + max-height: calc(200vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); + overflow-y: scroll; +} diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx index a4f076d48f6..f479038a7ab 100644 --- a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx +++ b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx @@ -15,7 +15,11 @@ import BookmarkItem from '../Bookmarks/BookmarkItem'; import styles from './BookmarkContents.module.scss'; -const BookmarkContents = (): JSX.Element => { +type Props = { + isExpanded: boolean +} +const BookmarkContents = (props: Props): JSX.Element => { + const { isExpanded } = props; const { t } = useTranslation(); const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(null); const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); @@ -39,38 +43,41 @@ const BookmarkContents = (): JSX.Element => { openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); }, [mutateCurrentUserBookmarks, openDeleteModal, t]); + return ( -
        - {bookmarkFolderData?.map((item) => { - return ( - - ); - })} -
        - {currentUserBookmarksData?.length === 0 ? t('No bookmarks yet') : ( - <> -
          - {currentUserBookmarksData?.map(page => ( - - ))} +
          +
            + {bookmarkFolderData?.map((item) => { + return ( + + ); + })} +
            + {currentUserBookmarksData?.length === 0 ? t('No bookmarks yet') : ( + <> +
              + {currentUserBookmarksData?.map(page => ( + + ))} -
            - - ) } -
            -
          +
        + + ) } +
        +
      +
      ); }; diff --git a/packages/app/src/styles/theme/_apply-colors-dark.scss b/packages/app/src/styles/theme/_apply-colors-dark.scss index 6b42197a698..d99041251cf 100644 --- a/packages/app/src/styles/theme/_apply-colors-dark.scss +++ b/packages/app/src/styles/theme/_apply-colors-dark.scss @@ -363,6 +363,13 @@ ul.pagination { } .grw-user-page-list-m { + .expand-compress-btn { + color: $body-color; + background-color: $body-bg; + } + .active { + background-color: lighten($body-bg, 12%), + } .grw-foldertree { @include override-list-group-item-for-pagetree( $body-color, diff --git a/packages/app/src/styles/theme/_apply-colors-light.scss b/packages/app/src/styles/theme/_apply-colors-light.scss index 968102e7f88..8a207c4920d 100644 --- a/packages/app/src/styles/theme/_apply-colors-light.scss +++ b/packages/app/src/styles/theme/_apply-colors-light.scss @@ -241,6 +241,13 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active; } .grw-user-page-list-m { + .expand-compress-btn { + color: $body-color; + background-color: $body-bg; + } + .active { + background-color: darken($body-bg, 12%), + } .grw-foldertree { @include override-list-group-item-for-pagetree( $body-color, From 4f69b1cd5e9a56f62a504c83d1ff618c2663d0d7 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 1 Dec 2022 16:28:56 +0800 Subject: [PATCH 090/826] Fix style and separate Expand and Compress Icon https://youtrack.weseek.co.jp/issue/GW-7850 - Create component CompressIcon and modify ExpandCompressIcon - Add grw prefix to expand-compress-btn class - Implement ExpandIcon and CompressIcon component - Update max-height value of expanded class - Move expand-compress-btn color definition to apply-colors --- .../app/src/components/Icons/CompressIcon.tsx | 19 ++++++++ .../components/Icons/ExpandCompressIcon.tsx | 43 ------------------- .../app/src/components/Icons/ExpandIcon.tsx | 20 +++++++++ .../UsersHomePageFooter.module.scss | 2 +- .../src/components/UsersHomePageFooter.tsx | 10 +++-- .../BookmarkContents.module.scss | 2 +- .../src/styles/theme/_apply-colors-dark.scss | 7 --- .../src/styles/theme/_apply-colors-light.scss | 7 --- .../app/src/styles/theme/_apply-colors.scss | 14 ++++++ 9 files changed, 62 insertions(+), 62 deletions(-) create mode 100644 packages/app/src/components/Icons/CompressIcon.tsx delete mode 100644 packages/app/src/components/Icons/ExpandCompressIcon.tsx create mode 100644 packages/app/src/components/Icons/ExpandIcon.tsx diff --git a/packages/app/src/components/Icons/CompressIcon.tsx b/packages/app/src/components/Icons/CompressIcon.tsx new file mode 100644 index 00000000000..4593a54df49 --- /dev/null +++ b/packages/app/src/components/Icons/CompressIcon.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const CompressIcon = ():JSX.Element => { + return ( + + + + ); +}; + +export default CompressIcon; diff --git a/packages/app/src/components/Icons/ExpandCompressIcon.tsx b/packages/app/src/components/Icons/ExpandCompressIcon.tsx deleted file mode 100644 index daf51b6fa14..00000000000 --- a/packages/app/src/components/Icons/ExpandCompressIcon.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; - -type Props = { - isExpanded: boolean -} -const ExpandCompressIcon = (props: Props): JSX.Element => { - const { isExpanded } = props; - - return ( - <> - {isExpanded ? ( - - - - ) : ( - - - - ) - } - - ); - -}; - -export default ExpandCompressIcon; diff --git a/packages/app/src/components/Icons/ExpandIcon.tsx b/packages/app/src/components/Icons/ExpandIcon.tsx new file mode 100644 index 00000000000..178b5f18c3a --- /dev/null +++ b/packages/app/src/components/Icons/ExpandIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +const ExpandIcon = (): JSX.Element => { + return ( + + + + ); +}; + +export default ExpandIcon; diff --git a/packages/app/src/components/UsersHomePageFooter.module.scss b/packages/app/src/components/UsersHomePageFooter.module.scss index f03fa53de8c..fde1faf3a99 100644 --- a/packages/app/src/components/UsersHomePageFooter.module.scss +++ b/packages/app/src/components/UsersHomePageFooter.module.scss @@ -32,7 +32,7 @@ height: 18px; } } - .expand-compress-btn { + .grw-expand-compress-btn { max-height: 40px; svg { width: 18px; diff --git a/packages/app/src/components/UsersHomePageFooter.tsx b/packages/app/src/components/UsersHomePageFooter.tsx index 5fd2a6c7831..5dd5b2caffc 100644 --- a/packages/app/src/components/UsersHomePageFooter.tsx +++ b/packages/app/src/components/UsersHomePageFooter.tsx @@ -10,7 +10,8 @@ import styles from '~/components/UsersHomePageFooter.module.scss'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput'; -import ExpandCompressIcon from './Icons/ExpandCompressIcon'; +import CompressIcon from './Icons/CompressIcon'; +import ExpandIcon from './Icons/ExpandIcon'; import FolderPlusIcon from './Icons/FolderPlusIcon'; import BookmarkContents from './UsersPageBookmarks/BookmarkContents'; @@ -56,10 +57,13 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss index 74dfe5305d4..739c8fb3f2a 100644 --- a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss +++ b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss @@ -10,7 +10,7 @@ $grw-sidebar-content-footer-height: 50px; } .expanded { - max-height: calc(200vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); + max-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); overflow-y: scroll; } diff --git a/packages/app/src/styles/theme/_apply-colors-dark.scss b/packages/app/src/styles/theme/_apply-colors-dark.scss index d99041251cf..6b42197a698 100644 --- a/packages/app/src/styles/theme/_apply-colors-dark.scss +++ b/packages/app/src/styles/theme/_apply-colors-dark.scss @@ -363,13 +363,6 @@ ul.pagination { } .grw-user-page-list-m { - .expand-compress-btn { - color: $body-color; - background-color: $body-bg; - } - .active { - background-color: lighten($body-bg, 12%), - } .grw-foldertree { @include override-list-group-item-for-pagetree( $body-color, diff --git a/packages/app/src/styles/theme/_apply-colors-light.scss b/packages/app/src/styles/theme/_apply-colors-light.scss index 8a207c4920d..968102e7f88 100644 --- a/packages/app/src/styles/theme/_apply-colors-light.scss +++ b/packages/app/src/styles/theme/_apply-colors-light.scss @@ -241,13 +241,6 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active; } .grw-user-page-list-m { - .expand-compress-btn { - color: $body-color; - background-color: $body-bg; - } - .active { - background-color: darken($body-bg, 12%), - } .grw-foldertree { @include override-list-group-item-for-pagetree( $body-color, diff --git a/packages/app/src/styles/theme/_apply-colors.scss b/packages/app/src/styles/theme/_apply-colors.scss index 4369df90203..188ae65ab7a 100644 --- a/packages/app/src/styles/theme/_apply-colors.scss +++ b/packages/app/src/styles/theme/_apply-colors.scss @@ -711,3 +711,17 @@ Emoji picker modal .emoji-picker-modal { background-color: transparent !important; } + +/* +Expand / compress button bookmark list on users page +*/ +.grw-user-page-list-m { + .grw-expand-compress-btn { + color: $body-color; + background-color: $body-bg; + &.active { + background-color: darken($body-bg, 12%), + } + } + +} From d3cb61fdb97744b4c6a0fa6817f18de2b814bed1 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Thu, 1 Dec 2022 19:08:20 +0800 Subject: [PATCH 091/826] Use same component for bookmark tree https://youtrack.weseek.co.jp/issue/GW-7849 - Adjust props of BookmarkItem on BookmarkFolderTree component - Change BookmarkItem component to previous version - Implement BookmarkFolderTree to UsersHomePageFooter component - Remove BookmarkContents for UsersHomePageFooter --- .../Bookmarks/BookmarkFolderTree.tsx | 1 - .../src/components/Bookmarks/BookmarkItem.tsx | 111 +++++------------- .../src/components/UsersHomePageFooter.tsx | 4 +- .../BookmarkContents.module.scss | 3 - .../UsersPageBookmarks/BookmarkContents.tsx | 77 ------------ 5 files changed, 33 insertions(+), 163 deletions(-) delete mode 100644 packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss delete mode 100644 packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx diff --git a/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx index 99af6a60e1e..978a5da6ac1 100644 --- a/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx @@ -69,7 +69,6 @@ const BookmarkFolderTree = (): JSX.Element => { onUnbookmarked={mutateCurrentUserBookmarks} onRenamed={mutateCurrentUserBookmarks} onClickDeleteMenuItem={deleteMenuItemClickHandler} - isSidebarItem={true} /> ); })} diff --git a/packages/app/src/components/Bookmarks/BookmarkItem.tsx b/packages/app/src/components/Bookmarks/BookmarkItem.tsx index 072c196e610..aa15e1d4948 100644 --- a/packages/app/src/components/Bookmarks/BookmarkItem.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkItem.tsx @@ -1,4 +1,3 @@ - import React, { useCallback, useState } from 'react'; import nodePath from 'path'; @@ -12,23 +11,22 @@ import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Put } from '~/client/util/apiv3-client'; import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page'; + import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; -import { PageListItemS } from '../PageList/PageListItemS'; type Props = { bookmarkedPage: IPageHasId, onUnbookmarked: () => void, onRenamed: () => void, - onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void, - isSidebarItem: boolean + onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void } const BookmarkItem = (props: Props): JSX.Element => { const { t } = useTranslation(); const { - bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, isSidebarItem, + bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, } = props; const [isRenameInputShown, setRenameInputShown] = useState(false); const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true); @@ -96,53 +94,9 @@ const BookmarkItem = (props: Props): JSX.Element => { onClickDeleteMenuItem(pageToDelete); }, [bookmarkedPage, onClickDeleteMenuItem]); - const renderBookmarkListForSidebar = () => { - return ( -
      -
    • - { isRenameInputShown ? ( - { setRenameInputShown(false) }} - onPressEnter={pressEnterForRenameHandler} - inputValidator={inputValidator} - /> - ) : ( - -

      {pageTitle}

      -
      - )} - - - - - - - { formerPagePath !== null ? `${formerPagePath}/` : '/' } - -
    • -
      - ); - }; - - const renderBookmarkListForUserPage = () => { - return ( -
    • + return ( +
      +
    • { isRenameInputShown ? ( { onPressEnter={pressEnterForRenameHandler} inputValidator={inputValidator} /> - ) : ( - <> -
      - - - - - - -
      - + +

      {pageTitle}

      +
      )} - + + + + + + + { formerPagePath !== null ? `${formerPagePath}/` : '/' } +
    • - ); - }; - return ( - <> - {isSidebarItem - ? renderBookmarkListForSidebar() - : renderBookmarkListForUserPage() - } - +
      ); }; diff --git a/packages/app/src/components/UsersHomePageFooter.tsx b/packages/app/src/components/UsersHomePageFooter.tsx index 7339d486048..1af45dbba01 100644 --- a/packages/app/src/components/UsersHomePageFooter.tsx +++ b/packages/app/src/components/UsersHomePageFooter.tsx @@ -10,8 +10,8 @@ import styles from '~/components/UsersHomePageFooter.module.scss'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput'; +import BookmarkFolderTree from './Bookmarks/BookmarkFolderTree'; import FolderPlusIcon from './Icons/FolderPlusIcon'; -import BookmarkContents from './UsersPageBookmarks/BookmarkContents'; export type UsersHomePageFooterProps = { @@ -65,7 +65,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
      )} { - + }
  • diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss deleted file mode 100644 index 961e3d1d3a8..00000000000 --- a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -@use '~/styles/molecules/bookmark-folder-tree'; -@use '~/styles/molecules/page_list'; - diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx deleted file mode 100644 index a4f076d48f6..00000000000 --- a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useCallback } from 'react'; - -import { useTranslation } from 'next-i18next'; - -import { toastSuccess } from '~/client/util/apiNotification'; -import { IPageToDeleteWithMeta } from '~/interfaces/page'; -import { OnDeletedFunction } from '~/interfaces/ui'; -import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; -import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; -import { usePageDeleteModal } from '~/stores/modal'; - -import BookmarkFolderItem from '../Bookmarks/BookmarkFolderItem'; -import BookmarkItem from '../Bookmarks/BookmarkItem'; - - -import styles from './BookmarkContents.module.scss'; - -const BookmarkContents = (): JSX.Element => { - const { t } = useTranslation(); - const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(null); - const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); - const { open: openDeleteModal } = usePageDeleteModal(); - - const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => { - const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { - if (typeof pathOrPathsToDelete !== 'string') { - return; - } - const path = pathOrPathsToDelete; - - if (isCompletely) { - toastSuccess(t('deleted_pages_completely', { path })); - } - else { - toastSuccess(t('deleted_pages', { path })); - } - mutateCurrentUserBookmarks(); - }; - openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); - }, [mutateCurrentUserBookmarks, openDeleteModal, t]); - - return ( -
      - {bookmarkFolderData?.map((item) => { - return ( - - ); - })} -
      - {currentUserBookmarksData?.length === 0 ? t('No bookmarks yet') : ( - <> -
        - {currentUserBookmarksData?.map(page => ( - - ))} - -
      - - ) } -
      -
    - ); -}; - -export default BookmarkContents; From 20124c6cf562c3798a5f0b3c5a7478b97b02ec55 Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 2 Dec 2022 18:38:37 +0800 Subject: [PATCH 092/826] Separate BookmarkFolderTree and Bookmark list https://youtrack.weseek.co.jp/issue/GW-7849 - Remove isSidebarItem props from BookmarkFolderItem component - Remove conditional rendering of bookmark folder item - Modify click event handler of Bookmark folder item - Separate BookmarkItem from BookmarkFolderTree - Create component for BookmarkItemList - Modify and implement BookmarkList in UsersHomePageFooter component - Adjust styling for Bookmark list on UsersHomePageFooter --- .../Bookmarks/BookmarkFolderItem.tsx | 86 ++++-------- .../Bookmarks/BookmarkFolderTree.tsx | 50 +------ .../components/Bookmarks/BookmarkItemList.tsx | 65 +++++++++ .../src/components/PageList/BookmarkList.tsx | 126 +++++++++++++++--- .../Sidebar/Bookmarks/BookmarkContents.tsx | 2 + .../UsersHomePageFooter.module.scss | 37 +++-- .../src/components/UsersHomePageFooter.tsx | 45 +++++++ .../src/styles/theme/_apply-colors-dark.scss | 12 ++ .../src/styles/theme/_apply-colors-light.scss | 12 ++ 9 files changed, 297 insertions(+), 138 deletions(-) create mode 100644 packages/app/src/components/Bookmarks/BookmarkItemList.tsx diff --git a/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx index ab78c008d24..5ada4163ea8 100644 --- a/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx @@ -21,10 +21,9 @@ import DeleteBookmarkFolderModal from './DeleteBookmarkFolderModal'; type BookmarkFolderItemProps = { bookmarkFolder: BookmarkFolderItems isOpen?: boolean - isSidebarItem: boolean } const BookmarkFolderItem: FC = (props: BookmarkFolderItemProps) => { - const { bookmarkFolder, isOpen: _isOpen = false, isSidebarItem } = props; + const { bookmarkFolder, isOpen: _isOpen = false } = props; const { t } = useTranslation(); const { @@ -39,7 +38,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt const [isCreateAction, setIsCreateAction] = useState(false); const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState(false); - const childCount = useCallback((): number => { + const getChildCount = useCallback((): number => { if (currentChildren != null && currentChildren.length > children.length) { return currentChildren.length; } @@ -121,7 +120,8 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt } }, [folderId, loadParent, t]); - const onClickPlusButton = useCallback(async() => { + const onClickPlusButton = useCallback(async(e) => { + e.stopPropagation(); if (!isOpen && hasChildren()) { setIsOpen(true); } @@ -135,7 +135,6 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt
    ); @@ -156,7 +155,7 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt return ( -
  • @@ -185,71 +184,38 @@ const BookmarkFolderItem: FC = (props: BookmarkFolderIt /> ) : ( <> -
    - {isSidebarItem ? ( -

    {name}

    - ) : ( -
    -
    -

    {name}

    -
    -
    -
    - - - - - - -
    -
    -
    - {hasChildren() && ( -
    - -
    - )} -
    -
    - )} +
    +

    {name}

    - {hasChildren() && isSidebarItem && ( + {hasChildren() && (
    - +
    )} ) } - { isSidebarItem && ( -
    - +
    + +
    e.stopPropagation()}> - - -
    - )} +
    +
    + + +
  • {isCreateAction && ( diff --git a/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx b/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx index 978a5da6ac1..584ab37fdc9 100644 --- a/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx +++ b/packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx @@ -1,74 +1,26 @@ -import { useCallback } from 'react'; - import { useTranslation } from 'next-i18next'; -import { toastSuccess } from '~/client/util/apiNotification'; -import { IPageToDeleteWithMeta } from '~/interfaces/page'; -import { OnDeletedFunction } from '~/interfaces/ui'; -import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; -import { usePageDeleteModal } from '~/stores/modal'; import BookmarkFolderItem from './BookmarkFolderItem'; -import BookmarkItem from './BookmarkItem'; import styles from './BookmarkFolderTree.module.scss'; const BookmarkFolderTree = (): JSX.Element => { const { t } = useTranslation(); - const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild(); - const { open: openDeleteModal } = usePageDeleteModal(); - - const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => { - const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { - if (typeof pathOrPathsToDelete !== 'string') { - return; - } - const path = pathOrPathsToDelete; - - if (isCompletely) { - toastSuccess(t('deleted_pages_completely', { path })); - } - else { - toastSuccess(t('deleted_pages', { path })); - } - mutateCurrentUserBookmarks(); - }; - openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); - }, [mutateCurrentUserBookmarks, openDeleteModal, t]); - return ( <> -
      +
        {bookmarkFolderData?.map((item) => { return ( - ); - })} - {currentUserBookmarksData?.length === 0 && ( -
        -
        - { t('No bookmarks yet') } -
        -
        - )} - { currentUserBookmarksData?.map((currentUserBookmark) => { - return ( - ); })} diff --git a/packages/app/src/components/Bookmarks/BookmarkItemList.tsx b/packages/app/src/components/Bookmarks/BookmarkItemList.tsx new file mode 100644 index 00000000000..701402e0a07 --- /dev/null +++ b/packages/app/src/components/Bookmarks/BookmarkItemList.tsx @@ -0,0 +1,65 @@ +import React, { useCallback } from 'react'; + +import { useTranslation } from 'next-i18next'; + +import { toastSuccess } from '~/client/util/apiNotification'; +import { IPageToDeleteWithMeta } from '~/interfaces/page'; +import { OnDeletedFunction } from '~/interfaces/ui'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; +import { usePageDeleteModal } from '~/stores/modal'; + +import BookmarkItem from './BookmarkItem'; + +import styles from './BookmarkFolderTree.module.scss'; + + +const BookmarkItemList = (): JSX.Element => { + const { t } = useTranslation(); + const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); + const { open: openDeleteModal } = usePageDeleteModal(); + + const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => { + const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + if (typeof pathOrPathsToDelete !== 'string') { + return; + } + const path = pathOrPathsToDelete; + + if (isCompletely) { + toastSuccess(t('deleted_pages_completely', { path })); + } + else { + toastSuccess(t('deleted_pages', { path })); + } + mutateCurrentUserBookmarks(); + }; + openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); + }, [mutateCurrentUserBookmarks, openDeleteModal, t]); + + return ( + <> + {currentUserBookmarksData?.length === 0 && ( +
        +
        + { t('No bookmarks yet') } +
        +
        + )} +
          + { currentUserBookmarksData?.map((currentUserBookmark) => { + return ( + + ); + })} +
        + + ); +}; + +export default BookmarkItemList; diff --git a/packages/app/src/components/PageList/BookmarkList.tsx b/packages/app/src/components/PageList/BookmarkList.tsx index c085bf49868..445fcc3f2ea 100644 --- a/packages/app/src/components/PageList/BookmarkList.tsx +++ b/packages/app/src/components/PageList/BookmarkList.tsx @@ -1,35 +1,123 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; +import nodePath from 'path'; + +import { + IPageInfoAll, IPageToDeleteWithMeta, pathUtils, +} from '@growi/core'; import { useTranslation } from 'next-i18next'; +import { DropdownToggle } from 'reactstrap'; -import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; +import { unbookmark } from '~/client/services/page-operation'; +import { toastError, toastSuccess } from '~/client/util/apiNotification'; +import { apiv3Put } from '~/client/util/apiv3-client'; +import { IPageHasId } from '~/interfaces/page'; import loggerFactory from '~/utils/logger'; +import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput'; +import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl'; + import { PageListItemS } from './PageListItemS'; const logger = loggerFactory('growi:BookmarkList'); -export const BookmarkList = (): JSX.Element => { - +type Props = { + page: IPageHasId + onRenamed: () => void + onUnbookmarked: () => void + onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void +} +export const BookmarkList = (props:Props): JSX.Element => { + const { + page, onRenamed, onUnbookmarked, onClickDeleteMenuItem, + } = props; const { t } = useTranslation(); - const { data: currentUserBookmarksData } = useSWRxCurrentUserBookmarks(); + const [isRenameInputShown, setIsRenameInputShown] = useState(false); + + const inputValidator = (title: string | null): AlertInfo | null => { + if (title == null || title === '' || title.trim() === '') { + return { + type: AlertType.WARNING, + message: t('form_validation.title_required'), + }; + } + + return null; + }; + + const bookmarkMenuItemClickHandler = useCallback(async() => { + await unbookmark(page._id); + onUnbookmarked(); + }, [page._id, onUnbookmarked]); + const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise => { + if (page._id == null || page.path == null) { + throw Error('_id and path must not be null.'); + } + + const pageToDelete: IPageToDeleteWithMeta = { + data: { + _id: page._id, + revision: page.revision as string, + path: page.path, + }, + meta: pageInfo, + }; + + onClickDeleteMenuItem(pageToDelete); + }, [onClickDeleteMenuItem, page]); + + const pressEnterForRenameHandler = useCallback(async(inputText: string) => { + const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? '')); + const newPagePath = nodePath.resolve(parentPath, inputText); + if (newPagePath === page.path) { + setIsRenameInputShown(false); + return; + } + + try { + setIsRenameInputShown(false); + await apiv3Put('/pages/rename', { + pageId: page._id, + revisionId: page.revision, + newPagePath, + }); + onRenamed(); + toastSuccess(t('renamed_pages', { path: page.path })); + } + catch (err) { + setIsRenameInputShown(true); + toastError(err); + } + }, [onRenamed, page, t]); return ( -
        - {currentUserBookmarksData?.length === 0 ? t('No bookmarks yet') : ( - <> -
          - - {currentUserBookmarksData?.map(page => ( -
        • - -
        • - ))} - -
        - +
      • + { isRenameInputShown ? ( + { setIsRenameInputShown(false) }} + onPressEnter={pressEnterForRenameHandler} + inputValidator={inputValidator} + /> + ) : ( + )} -
      • + + setIsRenameInputShown(true)} + onClickDeleteMenuItem={deleteMenuItemClickHandler} + > + + + + + + ); }; diff --git a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx index 928b5881269..b9b08c033e1 100644 --- a/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx +++ b/packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx @@ -6,6 +6,7 @@ import { toastError, toastSuccess } from '~/client/util/apiNotification'; import { apiv3Post } from '~/client/util/apiv3-client'; import BookmarkFolderNameInput from '~/components/Bookmarks/BookmarkFolderNameInput'; import BookmarkFolderTree from '~/components/Bookmarks/BookmarkFolderTree'; +import BookmarkItemList from '~/components/Bookmarks/BookmarkItemList'; import FolderPlusIcon from '~/components/Icons/FolderPlusIcon'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; @@ -65,6 +66,7 @@ const BookmarkContents = (): JSX.Element => { renderAddNewBookmarkFolder() } + ); }; diff --git a/packages/app/src/components/UsersHomePageFooter.module.scss b/packages/app/src/components/UsersHomePageFooter.module.scss index 65faf76f7b2..e68dbfbb4f0 100644 --- a/packages/app/src/components/UsersHomePageFooter.module.scss +++ b/packages/app/src/components/UsersHomePageFooter.module.scss @@ -2,24 +2,41 @@ .user-page-footer :global { .grw-user-page-list-m { - .list-group-item { - svg{ - width: 20px; - height: 20px; - } + .list-group{ + .list-group-item { + .grw-visible-on-hover { + display: none; + } - } - .grw-triangle-container{ - svg { - width: 12px; - height: 12px; + &:hover { + .grw-visible-on-hover { + display: block; + } + } + .grw-triangle-container{ + svg { + width: 12px; + height: 12px; + } + } + svg{ + width: 20px; + height: 20px; + } + min-height: 40px; + border-radius: 0px; } } + .grw-foldertree-item-container { input { max-width: 25%; } } + .grw-foldertree-title-anchor{ + width: fit-content !important; + margin-right: 20px; + } svg { width: 35px; height: 35px; diff --git a/packages/app/src/components/UsersHomePageFooter.tsx b/packages/app/src/components/UsersHomePageFooter.tsx index 1af45dbba01..06696f20f82 100644 --- a/packages/app/src/components/UsersHomePageFooter.tsx +++ b/packages/app/src/components/UsersHomePageFooter.tsx @@ -7,11 +7,16 @@ import { apiv3Post } from '~/client/util/apiv3-client'; import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon'; import { RecentCreated } from '~/components/RecentCreated/RecentCreated'; import styles from '~/components/UsersHomePageFooter.module.scss'; +import { IPageToDeleteWithMeta } from '~/interfaces/page'; +import { OnDeletedFunction } from '~/interfaces/ui'; +import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark'; import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder'; +import { usePageDeleteModal } from '~/stores/modal'; import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput'; import BookmarkFolderTree from './Bookmarks/BookmarkFolderTree'; import FolderPlusIcon from './Icons/FolderPlusIcon'; +import { BookmarkList } from './PageList/BookmarkList'; export type UsersHomePageFooterProps = { @@ -23,6 +28,27 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen const { creatorId } = props; const [isCreateAction, setIsCreateAction] = useState(false); const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null); + const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks(); + const { open: openDeleteModal } = usePageDeleteModal(); + + + const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => { + const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => { + if (typeof pathOrPathsToDelete !== 'string') { + return; + } + const path = pathOrPathsToDelete; + + if (isCompletely) { + toastSuccess(t('deleted_pages_completely', { path })); + } + else { + toastSuccess(t('deleted_pages', { path })); + } + mutateCurrentUserBookmarks(); + }; + openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler }); + }, [mutateCurrentUserBookmarks, openDeleteModal, t]); const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => { @@ -66,7 +92,26 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen )} { + } +
        +
        + {currentUserBookmarksData?.length === 0 + ? t('No bookmarks yet') + :
          + {currentUserBookmarksData?.map(page => ( + + ))} +
        + } +
        +
    diff --git a/packages/app/src/styles/theme/_apply-colors-dark.scss b/packages/app/src/styles/theme/_apply-colors-dark.scss index 6b42197a698..ca4fec53a6c 100644 --- a/packages/app/src/styles/theme/_apply-colors-dark.scss +++ b/packages/app/src/styles/theme/_apply-colors-dark.scss @@ -363,6 +363,7 @@ ul.pagination { } .grw-user-page-list-m { + @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active); .grw-foldertree { @include override-list-group-item-for-pagetree( $body-color, @@ -377,6 +378,17 @@ ul.pagination { @include mixins.button-outline-svg-icon-variant($secondary, $gray-200); } } + .page-list > .grw-bookmarks-list-container > .list-group { + @include override-list-group-item-for-pagetree( + $body-color, + lighten($body-bg, 8%), + lighten($body-bg, 15%), + darken($body-color, 15%), + darken($body-color, 10%), + lighten($body-bg, 18%), + lighten($body-bg, 24%) + ); + } } .btn.btn-page-item-control { diff --git a/packages/app/src/styles/theme/_apply-colors-light.scss b/packages/app/src/styles/theme/_apply-colors-light.scss index 968102e7f88..611868d49a9 100644 --- a/packages/app/src/styles/theme/_apply-colors-light.scss +++ b/packages/app/src/styles/theme/_apply-colors-light.scss @@ -241,6 +241,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active; } .grw-user-page-list-m { + @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active); .grw-foldertree { @include override-list-group-item-for-pagetree( $body-color, @@ -255,6 +256,17 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active; @include mixins.button-outline-svg-icon-variant($gray-400, $primary); } } + .page-list > .grw-bookmarks-list-container > .list-group { + @include override-list-group-item-for-pagetree( + $body-color, + darken($body-bg, 5%), + darken($body-bg, 12%), + lighten($body-color, 10%), + lighten($body-color, 8%), + darken($body-bg, 15%), + darken($body-bg, 24%) + ); + } } .btn.btn-page-item-control { From fca8226fe93f5da3b9bf203efd3e1d228ae5ffcf Mon Sep 17 00:00:00 2001 From: Mudana-Grune Date: Fri, 2 Dec 2022 18:53:53 +0800 Subject: [PATCH 093/826] Add prefix to css class https://youtrack.weseek.co.jp/issue/GW-7850 - Add prefix to expanded and compressed class --- .../UsersPageBookmarks/BookmarkContents.module.scss | 4 ++-- .../src/components/UsersPageBookmarks/BookmarkContents.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss index 739c8fb3f2a..a016f0129a6 100644 --- a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss +++ b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.module.scss @@ -4,12 +4,12 @@ $grw-sidebar-content-header-height: 58px; $grw-sidebar-content-footer-height: 50px; -.compressed { +.grw-bookarks-contents-compressed { max-height: calc(70vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); overflow-y: scroll; } -.expanded { +.grw-bookarks-contents-expanded { max-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height)); overflow-y: scroll; } diff --git a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx index f479038a7ab..e51f23de611 100644 --- a/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx +++ b/packages/app/src/components/UsersPageBookmarks/BookmarkContents.tsx @@ -45,7 +45,7 @@ const BookmarkContents = (props: Props): JSX.Element => { return ( -
    +