diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx index 15125aeb7d..cba01b834c 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx @@ -1,4 +1,5 @@ import { + AnimatedCollectionsIcon, AnimatedFolderIcon, type DropTargetDropEvent, type DropTargetOptions, @@ -6,7 +7,13 @@ import { MenuIcon, MenuItem, MenuSeparator, + MenuSub, } from '@affine/component'; +import { + useSelectCollection, + useSelectDoc, + useSelectTag, +} from '@affine/core/components/page-list/selector'; import { type FolderNode, OrganizeService, @@ -18,10 +25,14 @@ import { useI18n } from '@affine/i18n'; import { DeleteIcon, FolderIcon, + PageIcon, PlusIcon, + PlusThickIcon, RemoveFolderIcon, + TagsIcon, } from '@blocksuite/icons/rc'; import { DocsService, useLiveData, useServices } from '@toeverything/infra'; +import { difference } from 'lodash-es'; import { useCallback, useMemo, useState } from 'react'; import { ExplorerTreeNode, type ExplorerTreeNodeDropEffect } from '../../tree'; @@ -153,6 +164,9 @@ export const ExplorerFolderNodeFolder = ({ DocsService, WorkbenchService, }); + const openDocsSelector = useSelectDoc(); + const openTagsSelector = useSelectTag(); + const openCollectionsSelector = useSelectCollection(); const name = useLiveData(node.name$); const [collapsed, setCollapsed] = useState(true); const [newFolderId, setNewFolderId] = useState(null); @@ -479,6 +493,47 @@ export const ExplorerFolderNodeFolder = ({ setNewFolderId(newFolderId); }, [node, t]); + const handleAddToFolder = useCallback( + (type: 'doc' | 'collection' | 'tag') => { + const initialIds = children + .filter(node => node.type$.value === type) + .map(node => node.data$.value) + .filter(Boolean) as string[]; + const selector = + type === 'doc' + ? openDocsSelector + : type === 'collection' + ? openCollectionsSelector + : openTagsSelector; + selector(initialIds) + .then(selectedIds => { + const newItemIds = difference(selectedIds, initialIds); + const removedItemIds = difference(initialIds, selectedIds); + const removedItems = children.filter( + node => + !!node.data$.value && removedItemIds.includes(node.data$.value) + ); + + newItemIds.forEach(id => + node.createLink(type, id, node.indexAt('after')) + ); + removedItems.forEach(node => node.delete()); + const updated = newItemIds.length + removedItems.length; + updated && setCollapsed(false); + }) + .catch(err => { + console.error(`Unexpected error while selecting ${type}`, err); + }); + }, + [ + children, + node, + openCollectionsSelector, + openDocsSelector, + openTagsSelector, + ] + ); + const folderOperations = useMemo(() => { return [ { @@ -505,6 +560,63 @@ export const ExplorerFolderNodeFolder = ({ ), }, + { + index: 101, + view: ( + + + + } + onClick={() => handleAddToFolder('doc')} + > + {t['com.affine.rootAppSidebar.organize.folder.add-docs']()} + + ), + }, + { + index: 102, + view: ( + + + + ), + }} + items={ + <> + handleAddToFolder('tag')} + preFix={ + + + + } + > + {t['com.affine.rootAppSidebar.organize.folder.add-tags']()} + + handleAddToFolder('collection')} + preFix={ + + + + } + > + {t[ + 'com.affine.rootAppSidebar.organize.folder.add-collections' + ]()} + + + } + > + {t['com.affine.rootAppSidebar.organize.folder.add-others']()} + + ), + }, { index: 9999, view: , @@ -526,7 +638,7 @@ export const ExplorerFolderNodeFolder = ({ ), }, ]; - }, [handleCreateSubfolder, handleDelete, handleNewDoc, t]); + }, [handleAddToFolder, handleCreateSubfolder, handleDelete, handleNewDoc, t]); const finalOperations = useMemo(() => { if (additionalOperations) { diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 840bd39ea5..97fde1bd00 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1128,6 +1128,10 @@ "com.affine.rootAppSidebar.organize.empty": "No Folders", "com.affine.rootAppSidebar.organize.empty-folder": "Empty Folder", "com.affine.rootAppSidebar.organize.folder.create-subfolder": "Create a subfolder", + "com.affine.rootAppSidebar.organize.folder.add-docs": "Add docs", + "com.affine.rootAppSidebar.organize.folder.add-others": "Add others", + "com.affine.rootAppSidebar.organize.folder.add-tags": "Add Tags", + "com.affine.rootAppSidebar.organize.folder.add-collections": "Add Collections", "com.affine.rootAppSidebar.organize.empty-folder.add-pages": "Add pages", "com.affine.rootAppSidebar.organize.empty.new-folders-button": "New Folder", "com.affine.rootAppSidebar.organize.new-folders": "New Folder",