diff --git a/packages/frontend/component/src/ui/button/button.css.ts b/packages/frontend/component/src/ui/button/button.css.ts index 31126a7d55..196a0b4e9f 100644 --- a/packages/frontend/component/src/ui/button/button.css.ts +++ b/packages/frontend/component/src/ui/button/button.css.ts @@ -79,6 +79,10 @@ export const button = style({ lineHeight: lineHeightVar, selectors: { + // hover layer + '&[data-no-hover]:before, &[data-disabled]:before': { + display: 'none', + }, '&:hover:before': { opacity: 1 }, '&[data-block]': { display: 'flex' }, @@ -162,7 +166,6 @@ export const button = style({ // disabled '&[data-disabled]': { - cursor: 'not-allowed', opacity: 0.5, }, diff --git a/packages/frontend/component/src/ui/button/button.tsx b/packages/frontend/component/src/ui/button/button.tsx index 1428c4a4a2..df7a55d53d 100644 --- a/packages/frontend/component/src/ui/button/button.tsx +++ b/packages/frontend/component/src/ui/button/button.tsx @@ -43,6 +43,9 @@ export interface ButtonProps */ loading?: boolean; + /** No hover state */ + withoutHover?: boolean; + /** * By default, it is considered as an icon with preset size and color, * can be overridden by `prefixClassName` and `prefixStyle`. @@ -103,6 +106,7 @@ export const Button = forwardRef( block, loading, className, + withoutHover, prefix, prefixClassName, @@ -142,6 +146,7 @@ export const Button = forwardRef( data-disabled={disabled || undefined} data-size={size} data-variant={variant} + data-no-hover={withoutHover || undefined} onClick={handleClick} > ), }, + + // favorite, only new favorite available + ...(runtimeConfig.enableNewFavorite && node.id + ? [ + { + index: 200, + view: , + }, + ] + : []), + { index: 9999, view: , @@ -731,7 +745,14 @@ export const ExplorerFolderNodeFolder = ({ ), }, ]; - }, [handleAddToFolder, handleCreateSubfolder, handleDelete, handleNewDoc, t]); + }, [ + handleAddToFolder, + handleCreateSubfolder, + handleDelete, + handleNewDoc, + node.id, + t, + ]); const finalOperations = useMemo(() => { if (additionalOperations) { diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/folder/operations.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/folder/operations.tsx new file mode 100644 index 0000000000..14ffdd1640 --- /dev/null +++ b/packages/frontend/core/src/modules/explorer/views/nodes/folder/operations.tsx @@ -0,0 +1,39 @@ +import { MenuIcon, MenuItem } from '@affine/component'; +import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; +import { useI18n } from '@affine/i18n'; +import { FavoritedIcon, FavoriteIcon } from '@blocksuite/icons/rc'; +import { useLiveData, useService } from '@toeverything/infra'; +import { cssVar } from '@toeverything/theme'; +import { useMemo } from 'react'; + +export const FavoriteFolderOperation = ({ id }: { id: string }) => { + const t = useI18n(); + const compatibleFavoriteItemsAdapter = useService( + CompatibleFavoriteItemsAdapter + ); + + const favorite = useLiveData( + useMemo(() => { + return compatibleFavoriteItemsAdapter.isFavorite$(id, 'folder'); + }, [compatibleFavoriteItemsAdapter, id]) + ); + + return ( + + {favorite ? ( + + ) : ( + + )} + + } + onClick={() => compatibleFavoriteItemsAdapter.toggle(id, 'folder')} + > + {favorite + ? t['com.affine.rootAppSidebar.organize.folder-rm-favorite']() + : t['com.affine.rootAppSidebar.organize.folder-add-favorite']()} + + ); +}; diff --git a/packages/frontend/core/src/modules/properties/services/adapter.ts b/packages/frontend/core/src/modules/properties/services/adapter.ts index 3674800057..cbefa6a4f4 100644 --- a/packages/frontend/core/src/modules/properties/services/adapter.ts +++ b/packages/frontend/core/src/modules/properties/services/adapter.ts @@ -7,7 +7,7 @@ import { LiveData, Service } from '@toeverything/infra'; import { defaultsDeep } from 'lodash-es'; import { Observable } from 'rxjs'; -import type { FavoriteService } from '../../favorite'; +import type { FavoriteService, FavoriteSupportType } from '../../favorite'; import { PagePropertyType, PageSystemPropertyId, @@ -305,6 +305,10 @@ export class FavoriteItemsAdapter extends Service { } } +type CompatibleFavoriteSupportType = + | WorkspaceFavoriteItem['type'] + | FavoriteSupportType; + /** * A service written for compatibility,with the same API as FavoriteItemsAdapter. * When `runtimeConfig.enableNewFavorite` is false, it operates FavoriteItemsAdapter, @@ -318,24 +322,29 @@ export class CompatibleFavoriteItemsAdapter extends Service { super(); } - toggle(id: string, type: WorkspaceFavoriteItem['type']) { - if (runtimeConfig.enableNewFavorite) { + // old adapter only supports doc and collection + private isNewType(type: CompatibleFavoriteSupportType) { + return type !== 'doc' && type !== 'collection'; + } + + toggle(id: string, type: CompatibleFavoriteSupportType) { + if (runtimeConfig.enableNewFavorite || this.isNewType(type)) { this.favoriteService.favoriteList.toggle(type, id); } else { this.favoriteItemsAdapter.toggle(id, type); } } - isFavorite$(id: string, type: WorkspaceFavoriteItem['type']) { - if (runtimeConfig.enableNewFavorite) { + isFavorite$(id: string, type: CompatibleFavoriteSupportType) { + if (runtimeConfig.enableNewFavorite || this.isNewType(type)) { return this.favoriteService.favoriteList.isFavorite$(type, id); } else { return this.favoriteItemsAdapter.isFavorite$(id, type); } } - isFavorite(id: string, type: WorkspaceFavoriteItem['type']) { - if (runtimeConfig.enableNewFavorite) { + isFavorite(id: string, type: CompatibleFavoriteSupportType) { + if (runtimeConfig.enableNewFavorite || this.isNewType(type)) { return this.favoriteService.favoriteList.isFavorite$(type, id).value; } else { return this.favoriteItemsAdapter.isFavorite(id, type); diff --git a/packages/frontend/core/src/pages/workspace/page-list-empty.css.ts b/packages/frontend/core/src/pages/workspace/page-list-empty.css.ts index b886d137bd..bc7826f63b 100644 --- a/packages/frontend/core/src/pages/workspace/page-list-empty.css.ts +++ b/packages/frontend/core/src/pages/workspace/page-list-empty.css.ts @@ -1,4 +1,5 @@ import { cssVar } from '@toeverything/theme'; +import { cssVarV2 } from '@toeverything/theme/v2'; import { style } from '@vanilla-extract/css'; export const pageListEmptyStyle = style({ height: 'calc(100% - 52px)', @@ -23,3 +24,15 @@ export const emptyDescKbd = style([ cursor: 'text', }, ]); + +export const plusButton = style({ + borderWidth: 1, + borderColor: cssVarV2('layer/border'), + boxShadow: 'none', + cursor: 'default', +}); +export const descWrapper = style({ + display: 'flex', + alignItems: 'center', + gap: 8, +}); diff --git a/packages/frontend/core/src/pages/workspace/page-list-empty.tsx b/packages/frontend/core/src/pages/workspace/page-list-empty.tsx index 4acdf97da8..df4f808913 100644 --- a/packages/frontend/core/src/pages/workspace/page-list-empty.tsx +++ b/packages/frontend/core/src/pages/workspace/page-list-empty.tsx @@ -1,33 +1,30 @@ -import { Empty } from '@affine/component'; +import { Empty, IconButton } from '@affine/component'; import { Trans, useI18n } from '@affine/i18n'; +import { PlusIcon } from '@blocksuite/icons/rc'; import type { DocCollection } from '@blocksuite/store'; import type { ReactNode } from 'react'; -import { useCallback } from 'react'; -import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils'; import * as styles from './page-list-empty.css'; export const EmptyPageList = ({ type, - docCollection, heading, }: { type: 'all' | 'trash' | 'shared' | 'public'; docCollection: DocCollection; heading?: ReactNode; }) => { - const { createPage } = usePageHelper(docCollection); const t = useI18n(); - const onCreatePage = useCallback(() => { - createPage?.(); - }, [createPage]); const getEmptyDescription = () => { if (type === 'all') { const createNewPageButton = ( - + } + /> ); if (environment.isDesktop) { const shortcut = environment.isMacOs ? '⌘ + N' : 'Ctrl + N'; @@ -61,7 +58,9 @@ export const EmptyPageList = ({ {heading &&
{heading}
} {getEmptyDescription()} + } /> ); diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 90601046cd..9ffcc3839b 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1118,12 +1118,12 @@ "com.affine.rootAppSidebar.collections": "Collections", "com.affine.rootAppSidebar.favorites": "Favourites", "com.affine.rootAppSidebar.migration-data": "Migration data", - "com.affine.rootAppSidebar.migration-data.help": "Migration your favorites data", - "com.affine.rootAppSidebar.migration-data.help.description": "This is the old Favourites data. Previously, Favourites were shared during collaboration. We have redesigned the Favourites feature, which requires you to take certain actions. You can drag this content or directly hide and delete this part of the data.", - "com.affine.rootAppSidebar.migration-data.help.clean-all": "Clear all migration data", + "com.affine.rootAppSidebar.migration-data.help": "The old \"Favorites\" will be replaced", + "com.affine.rootAppSidebar.migration-data.help.description": "Your documents are safe, yet you may need to re-pin your most-used ones. Previously, 'Favorites' were shared across the workspace. We've improved on this - now each person has a personal 'Favorites' section for their top documents, collections, and folders. We advise migrating your data.\nThe old 'Favorites' will disappear once emptied. Drag your items with ease from the old shared 'Favorites' into your new personal section, or opt to delete all old favorites by simply clicking 'Delete all the old data' now.", + "com.affine.rootAppSidebar.migration-data.help.clean-all": "Delete all the old data", "com.affine.rootAppSidebar.migration-data.help.confirm": "OK", - "com.affine.rootAppSidebar.migration-data.clean-all": "Clean all migration data", - "com.affine.rootAppSidebar.migration-data.clean-all.description": "This will delete all Migration data, don't worry, it won't delete any data entities, it will just clean up the content on the sidebar. Once the content is cleared, the sidebar will no longer display this section until the next occurrence of conflicting data.", + "com.affine.rootAppSidebar.migration-data.clean-all": "Delete All Old Data", + "com.affine.rootAppSidebar.migration-data.clean-all.description": "This action will empty and delete the entire discontinued Favorites section. Don't worry, all your documents will not be affected - this only removes the old Favorites from the sidebar to place the new one. In the meantime, please verify that you've moved all your frequently accessed documents to the spanking new personal Favorites section first.", "com.affine.rootAppSidebar.migration-data.clean-all.confirm": "OK", "com.affine.rootAppSidebar.migration-data.clean-all.cancel": "Cancel", "com.affine.rootAppSidebar.organize": "Organize", @@ -1140,6 +1140,8 @@ "com.affine.rootAppSidebar.organize.delete": "Delete", "com.affine.rootAppSidebar.organize.delete-from-folder": "Remove from folder", "com.affine.rootAppSidebar.organize.root-folder-only": "Only folder can be placed on here", + "com.affine.rootAppSidebar.organize.folder-add-favorite": "Add to favorites", + "com.affine.rootAppSidebar.organize.folder-rm-favorite": "Remove from favorites", "com.affine.rootAppSidebar.tags": "Tags", "com.affine.rootAppSidebar.tags.empty": "No Tags", "com.affine.rootAppSidebar.tags.new-tag": "New Tag", @@ -1393,8 +1395,8 @@ "com.affine.syncing": "Syncing", "core": "core", "dark": "Dark", - "emptyAllPages": "Click on the <1>$t(New Doc) button to create your first doc.", - "emptyAllPagesClient": "Click on the <1>$t(New Doc) button Or press <3>{{shortcut}} to create your first doc.", + "emptyAllPages": "Click on the <1> button to create your first doc.", + "emptyAllPagesClient": "Click on the <1> button Or press <3>{{shortcut}} to create your first doc.", "emptyFavorite": "Click Add to Favourites and the doc will appear here.", "emptySharedPages": "Shared docs will appear here.", "emptyTrash": "Click Add to Trash and the doc will appear here.",