Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update bookmarks tree move to root features #7586

3 changes: 2 additions & 1 deletion apps/app/public/static/locales/en_US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,8 @@
"new_folder": "New Folder",
"delete": "Delete Folder",
"drop_item_here": "Drag and drop item here",
"cancel_bookmark": "Un-bookmark this page"
"cancel_bookmark": "Un-bookmark this page",
"Move to the root": "Move to the root"
},
"v5_page_migration": {
"page_tree_not_avaliable" : "Page tree feature is not available yet.",
Expand Down
3 changes: 2 additions & 1 deletion apps/app/public/static/locales/ja_JP/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,8 @@
"new_folder": "新しいフォルダ",
"delete": "フォルダを削除",
"drop_item_here": "ルートに配置する",
"cancel_bookmark": "このページのブックマークを解除"
"cancel_bookmark": "このページのブックマークを解除",
"move_to_root": "ルートに配置する"
},
"v5_page_migration": {
"page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
Expand Down
3 changes: 2 additions & 1 deletion apps/app/public/static/locales/zh_CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,8 @@
"new_folder": "新建文件夹",
"delete": "删除文件夹",
"drop_item_here": "将项目拖放到此处",
"cancel_bookmark": "取消收藏此页面"
"cancel_bookmark": "取消收藏此页面",
"move_to_root": "移动到根部"
},
"v5_page_migration": {
"page_tree_not_avaliable": "Page Tree 功能不可用",
Expand Down
92 changes: 35 additions & 57 deletions apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,53 @@ import {
FC, useCallback, useState,
} from 'react';

import { useTranslation } from 'next-i18next';
import { DropdownToggle } from 'reactstrap';

import {
addBookmarkToFolder, addNewFolder, hasChildren, updateBookmarkFolder,
} from '~/client/util/bookmark-utils';
import { toastError, toastSuccess } from '~/client/util/toastr';
import { toastError } from '~/client/util/toastr';
import { FolderIcon } from '~/components/Icons/FolderIcon';
import { TriangleIcon } from '~/components/Icons/TriangleIcon';
import {
BookmarkFolderItems, DragItemDataType, DragItemType, DRAG_ITEM_TYPE,
} from '~/interfaces/bookmark-info';
import { IPageToDeleteWithMeta } from '~/interfaces/page';
import { onDeletedBookmarkFolderFunction, OnDeletedFunction } from '~/interfaces/ui';
import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
import { useBookmarkFolderDeleteModal, usePageDeleteModal } from '~/stores/modal';
import { useSWRxCurrentPage } from '~/stores/page';
import { onDeletedBookmarkFolderFunction } from '~/interfaces/ui';
import { useBookmarkFolderDeleteModal } from '~/stores/modal';

import { BookmarkFolderItemControl } from './BookmarkFolderItemControl';
import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
import { BookmarkItem } from './BookmarkItem';
import { DragAndDropWrapper } from './DragAndDropWrapper';


type BookmarkFolderItemProps = {
bookmarkFolder: BookmarkFolderItems
isOpen?: boolean
level: number
root: string
isUserHomePage?: boolean
onClickDeleteBookmarkHandler: (pageToDelete: IPageToDeleteWithMeta) => void
bookmarkFolderTreeMutation: () => void
}

export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
const BASE_FOLDER_PADDING = 15;
const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
const {
bookmarkFolder, isOpen: _isOpen = false, level, root, isUserHomePage,
onClickDeleteBookmarkHandler, bookmarkFolderTreeMutation,
} = props;

const { t } = useTranslation();
const {
name, _id: folderId, children, parent, bookmarks,
} = bookmarkFolder;

const [targetFolder, setTargetFolder] = useState<string | null>(folderId);
const [isOpen, setIsOpen] = useState(_isOpen);
const { mutate: mutateBookmarkData } = useSWRxBookamrkFolderAndChild();
const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
const [isRenameAction, setIsRenameAction] = useState<boolean>(false);
const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
const { data: currentPage } = useSWRxCurrentPage();
const { mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
const { open: openDeleteModal } = usePageDeleteModal();

const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();

const childrenExists = hasChildren(children);
Expand All @@ -67,31 +60,30 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
setTargetFolder(folderId);
}, [folderId, isOpen]);

// Rename for bookmark folder handler
// Rename for bookmark folder handler
const onPressEnterHandlerForRename = useCallback(async(folderName: string) => {
try {
await updateBookmarkFolder(folderId, folderName, parent);
mutateBookmarkData();
bookmarkFolderTreeMutation();
setIsRenameAction(false);
}
catch (err) {
toastError(err);
}
}, [folderId, mutateBookmarkData, parent]);
}, [bookmarkFolderTreeMutation, folderId, parent]);

// Create new folder / subfolder handler
const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
try {
await addNewFolder(folderName, targetFolder);
setIsOpen(true);
setIsCreateAction(false);
mutateBookmarkData();
bookmarkFolderTreeMutation();
}
catch (err) {
toastError(err);
}
}, [mutateBookmarkData, targetFolder]);

}, [bookmarkFolderTreeMutation, targetFolder]);

const onClickPlusButton = useCallback(async(e) => {
e.stopPropagation();
Expand All @@ -101,37 +93,12 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
setIsCreateAction(true);
}, [childrenExists, isOpen]);

const onClickDeleteBookmarkHandler = 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 }));
}
mutateBookmarkData();
mutateBookmarkInfo();
};
openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
}, [mutateBookmarkInfo, mutateBookmarkData, openDeleteModal, t]);

const onUnbookmarkHandler = useCallback(() => {
mutateBookmarkData();
mutateBookmarkInfo();
}, [mutateBookmarkInfo, mutateBookmarkData]);


const itemDropHandler = async(item: DragItemDataType, dragItemType: string | symbol | null) => {
if (dragItemType === DRAG_ITEM_TYPE.FOLDER) {
try {
if (item.bookmarkFolder != null) {
await updateBookmarkFolder(item.bookmarkFolder._id, item.bookmarkFolder.name, bookmarkFolder._id);
mutateBookmarkData();
bookmarkFolderTreeMutation();
}
}
catch (err) {
Expand All @@ -142,8 +109,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
try {
if (item != null) {
await addBookmarkToFolder(item._id, bookmarkFolder._id);
mutateBookmarkData();
await mutateUserBookmarks();
bookmarkFolderTreeMutation();
}
}
catch (err) {
Expand Down Expand Up @@ -184,7 +150,9 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
bookmarkFolder={childFolder}
level={level + 1}
root={root}
isUserHomePage ={isUserHomePage}
isUserHomePage={isUserHomePage}
onClickDeleteBookmarkHandler={onClickDeleteBookmarkHandler}
bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
/>
</div>
);
Expand All @@ -195,13 +163,13 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
return isOpen && bookmarks?.map((bookmark) => {
return (
<BookmarkItem
bookmarkedPage={bookmark.page}
key={bookmark._id}
onUnbookmarked={onUnbookmarkHandler}
onRenamed={mutateBookmarkData}
onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
parentFolder={bookmarkFolder}
bookmarkedPage={bookmark.page}
level={level + 1}
parentFolder={bookmarkFolder}
isMoveToRoot={true}
onClickDeleteBookmarkHandler={onClickDeleteBookmarkHandler}
bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
/>
);
});
Expand All @@ -216,16 +184,24 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
if (typeof folderId !== 'string') {
return;
}
mutateBookmarkInfo();
mutateBookmarkData();
bookmarkFolderTreeMutation();
};

if (bookmarkFolder == null) {
return;
}
openDeleteBookmarkFolderModal(bookmarkFolder, { onDeleted: bookmarkFolderDeleteHandler });
}, [bookmarkFolder, mutateBookmarkData, mutateBookmarkInfo, openDeleteBookmarkFolderModal]);
}, [bookmarkFolder, bookmarkFolderTreeMutation, openDeleteBookmarkFolderModal]);

const onClickMoveToRootHandlerForBookmarkFolderItemControl = useCallback(async() => {
try {
await updateBookmarkFolder(bookmarkFolder._id, bookmarkFolder.name, null);
bookmarkFolderTreeMutation();
}
catch (err) {
toastError(err);
}
}, [bookmarkFolder._id, bookmarkFolder.name, bookmarkFolderTreeMutation]);

return (
<div id={`grw-bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container">
Expand Down Expand Up @@ -278,6 +254,8 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
<BookmarkFolderItemControl
onClickRename={onClickRenameHandler}
onClickDelete={onClickDeleteHandler}
onClickMoveToRoot={onClickMoveToRootHandlerForBookmarkFolderItemControl}
isMoveToRoot={bookmarkFolder.parent != null}
>
<div onClick={e => e.stopPropagation()}>
<DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
Expand Down
33 changes: 25 additions & 8 deletions apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import {
Dropdown, DropdownItem, DropdownMenu, DropdownToggle,
} from 'reactstrap';


type BookmarkFolderItemControlProps = {
export const BookmarkFolderItemControl: React.FC<{
children?: React.ReactNode
isMoveToRoot: boolean
onClickMoveToRoot: () => void
onClickRename: () => void
onClickDelete: () => void
}
export const BookmarkFolderItemControl = (props: BookmarkFolderItemControlProps): JSX.Element => {
}> = ({
children,
isMoveToRoot,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onClickMoveToRoot がセットされているかどうかで代用できるのでは?
代用できないのだとしたら変数名を canMoveToRoot に変更する

onClickMoveToRoot,
onClickRename,
onClickDelete,
}): JSX.Element => {
const { t } = useTranslation();
const { children, onClickRename, onClickDelete } = props;
const [isOpen, setIsOpen] = useState(false);

return (
<Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)}>
{ children ?? (
Expand All @@ -23,18 +29,29 @@ export const BookmarkFolderItemControl = (props: BookmarkFolderItemControlProps)
</DropdownToggle>
) }
<DropdownMenu
positionFixed
modifiers={{ preventOverflow: { boundariesElement: undefined } }}
right={true}
modifiers={{ preventOverflow: { boundariesElement: 'viewport' } }}
container="body"
style={{ zIndex: 1055 }} /* make it larger than $zindex-modal of bootstrap */
jam411 marked this conversation as resolved.
Show resolved Hide resolved
>
{isMoveToRoot && (
<DropdownItem
onClick={onClickMoveToRoot}
className="grw-page-control-dropdown-item"
>
<i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
{t('bookmark_folder.move_to_root')}
</DropdownItem>
)}
<DropdownItem
onClick={onClickRename}
className="grw-page-control-dropdown-item"
>
<i className="icon-fw icon-action-redo grw-page-control-dropdown-icon"></i>
{t('Rename')}
</DropdownItem>

<DropdownItem divider/>

<DropdownItem
className='pt-2 grw-page-control-dropdown-item text-danger'
onClick={onClickDelete}
Expand Down
Loading