mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 10:36:22 +08:00
Vendored
-7
@@ -31,13 +31,6 @@ export const runtimeFlagsSchema = z.object({
|
||||
enableExperimentalFeature: z.boolean(),
|
||||
enableInfoModal: z.boolean(),
|
||||
enableOrganize: z.boolean(),
|
||||
// show the new favorite, which exclusive to each user
|
||||
enableNewFavorite: z.boolean(),
|
||||
// show the old favorite
|
||||
enableOldFavorite: z.boolean(),
|
||||
// before 0.16, enableNewFavorite = false and enableOldFavorite = true
|
||||
// after 0.16, enableNewFavorite = true and enableOldFavorite = false
|
||||
// for debug purpose, we can enable both
|
||||
});
|
||||
|
||||
export type RuntimeConfig = z.infer<typeof runtimeFlagsSchema>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
@@ -16,7 +16,7 @@ import { VirtualizedList } from '../virtualized-list';
|
||||
|
||||
const FavoriteOperation = ({ collection }: { collection: ListItem }) => {
|
||||
const t = useI18n();
|
||||
const favAdapter = useService(FavoriteItemsAdapter);
|
||||
const favAdapter = useService(CompatibleFavoriteItemsAdapter);
|
||||
const isFavorite = useLiveData(
|
||||
favAdapter.isFavorite$(collection.id, 'collection')
|
||||
);
|
||||
|
||||
@@ -211,16 +211,14 @@ export const PageOperationCell = ({
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{runtimeConfig.enableNewFavorite && (
|
||||
<ColWrapper
|
||||
hideInSmallContainer
|
||||
data-testid="page-list-item-favorite"
|
||||
data-favorite={favourite ? true : undefined}
|
||||
className={styles.favoriteCell}
|
||||
>
|
||||
<FavoriteTag onClick={onToggleFavoritePage} active={favourite} />
|
||||
</ColWrapper>
|
||||
)}
|
||||
<ColWrapper
|
||||
hideInSmallContainer
|
||||
data-testid="page-list-item-favorite"
|
||||
data-favorite={favourite ? true : undefined}
|
||||
className={styles.favoriteCell}
|
||||
>
|
||||
<FavoriteTag onClick={onToggleFavoritePage} active={favourite} />
|
||||
</ColWrapper>
|
||||
<ColWrapper alignment="start">
|
||||
<Menu
|
||||
items={OperationMenu}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { FavoriteService } from '@affine/core/modules/favorite';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { FavoriteTag } from '../components/favorite-tag';
|
||||
import { tagHeaderColsDef } from '../header-col-def';
|
||||
import { TagListItemRenderer } from '../page-group';
|
||||
import { ListTableHeader } from '../page-header';
|
||||
@@ -11,32 +14,29 @@ import { SelectorLayout } from '../selector/selector-layout';
|
||||
import type { ListItem, TagMeta } from '../types';
|
||||
import { VirtualizedList } from '../virtualized-list';
|
||||
|
||||
// TODO(@EYHN): add tag to favourite support
|
||||
const FavoriteOperation = ({ tag: _ }: { tag: ListItem }) => {
|
||||
// const t = useI18n();
|
||||
// const favAdapter = useService(FavoriteItemsAdapter);
|
||||
// const isFavorite = useLiveData(
|
||||
// favAdapter.isFavorite$(tag.id, 'tag')
|
||||
// );
|
||||
const FavoriteOperation = ({ tag }: { tag: ListItem }) => {
|
||||
const t = useI18n();
|
||||
const favoriteService = useService(FavoriteService);
|
||||
const isFavorite = useLiveData(
|
||||
favoriteService.favoriteList.isFavorite$('tag', tag.id)
|
||||
);
|
||||
|
||||
// const onToggleFavoriteCollection = useCallback(() => {
|
||||
// favAdapter.toggle(tag.id, 'tag');
|
||||
// toast(
|
||||
// isFavorite
|
||||
// ? t['com.affine.toastMessage.removedFavorites']()
|
||||
// : t['com.affine.toastMessage.addedFavorites']()
|
||||
// );
|
||||
// }, [tag.id, favAdapter, isFavorite, t]);
|
||||
const onToggleFavoriteCollection = useCallback(() => {
|
||||
favoriteService.favoriteList.toggle('tag', tag.id);
|
||||
toast(
|
||||
isFavorite
|
||||
? t['com.affine.toastMessage.removedFavorites']()
|
||||
: t['com.affine.toastMessage.addedFavorites']()
|
||||
);
|
||||
}, [favoriteService.favoriteList, tag.id, isFavorite, t]);
|
||||
|
||||
// return (
|
||||
// <FavoriteTag
|
||||
// style={{ marginRight: 8 }}
|
||||
// onClick={onToggleFavoriteCollection}
|
||||
// active={isFavorite}
|
||||
// />
|
||||
// );
|
||||
|
||||
return null;
|
||||
return (
|
||||
<FavoriteTag
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={onToggleFavoriteCollection}
|
||||
active={isFavorite}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectTag = ({
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ExplorerCollections,
|
||||
ExplorerFavorites,
|
||||
ExplorerMigrationFavorites,
|
||||
ExplorerOldFavorites,
|
||||
ExplorerOrganize,
|
||||
} from '@affine/core/modules/explorer';
|
||||
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
|
||||
@@ -169,10 +168,9 @@ export const RootAppSidebar = (): ReactElement => {
|
||||
</MenuItem>
|
||||
</SidebarContainer>
|
||||
<SidebarScrollableContainer>
|
||||
{runtimeConfig.enableNewFavorite && <ExplorerFavorites />}
|
||||
<ExplorerFavorites />
|
||||
{runtimeConfig.enableOrganize && <ExplorerOrganize />}
|
||||
{runtimeConfig.enableNewFavorite && <ExplorerMigrationFavorites />}
|
||||
{runtimeConfig.enableOldFavorite && <ExplorerOldFavorites />}
|
||||
<ExplorerMigrationFavorites />
|
||||
<ExplorerCollections />
|
||||
<ExplorerTags />
|
||||
<CategoryDivider label={t['com.affine.rootAppSidebar.others']()} />
|
||||
|
||||
@@ -11,7 +11,6 @@ export type { CollapsibleSectionName } from './types';
|
||||
export { ExplorerCollections } from './views/sections/collections';
|
||||
export { ExplorerFavorites } from './views/sections/favorites';
|
||||
export { ExplorerMigrationFavorites } from './views/sections/migration-favorites';
|
||||
export { ExplorerOldFavorites } from './views/sections/old-favorites';
|
||||
export { ExplorerOrganize } from './views/sections/organize';
|
||||
|
||||
export function configureExplorerModule(framework: Framework) {
|
||||
|
||||
@@ -675,15 +675,10 @@ export const ExplorerFolderNodeFolder = ({
|
||||
),
|
||||
},
|
||||
|
||||
// favorite, only new favorite available
|
||||
...(runtimeConfig.enableNewFavorite && node.id
|
||||
? [
|
||||
{
|
||||
index: 200,
|
||||
view: <FavoriteFolderOperation id={node.id} />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
index: 200,
|
||||
view: node.id ? <FavoriteFolderOperation id={node.id} /> : null,
|
||||
},
|
||||
|
||||
{
|
||||
index: 9999,
|
||||
|
||||
@@ -133,33 +133,29 @@ export const useExplorerTagNodeOperations = (
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(runtimeConfig.enableNewFavorite
|
||||
? [
|
||||
{
|
||||
index: 199,
|
||||
view: (
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
{favorite ? (
|
||||
<FavoritedIcon
|
||||
style={{ color: 'var(--affine-primary-color)' }}
|
||||
/>
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)}
|
||||
</MenuIcon>
|
||||
}
|
||||
onClick={handleToggleFavoriteTag}
|
||||
>
|
||||
{favorite
|
||||
? t['com.affine.favoritePageOperation.remove']()
|
||||
: t['com.affine.favoritePageOperation.add']()}
|
||||
</MenuItem>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
index: 199,
|
||||
view: (
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
{favorite ? (
|
||||
<FavoritedIcon
|
||||
style={{ color: 'var(--affine-primary-color)' }}
|
||||
/>
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)}
|
||||
</MenuIcon>
|
||||
}
|
||||
onClick={handleToggleFavoriteTag}
|
||||
>
|
||||
{favorite
|
||||
? t['com.affine.favoritePageOperation.remove']()
|
||||
: t['com.affine.favoritePageOperation.add']()}
|
||||
</MenuItem>
|
||||
),
|
||||
},
|
||||
{
|
||||
index: 9999,
|
||||
view: <MenuSeparator key="menu-separator" />,
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ export const ExplorerMigrationFavorites = () => {
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const favorites = useLiveData(
|
||||
favoriteItemsAdapter.orderedFavorites$.map(favs => {
|
||||
favoriteItemsAdapter.favorites$.map(favs => {
|
||||
return favs.filter(fav => {
|
||||
if (fav.type === 'doc') {
|
||||
return (
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const content = style({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
padding: '9px 20px 25px 21px',
|
||||
});
|
||||
export const iconWrapper = style({
|
||||
width: 36,
|
||||
height: 36,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: cssVar('hoverColor'),
|
||||
});
|
||||
export const icon = style({
|
||||
fontSize: 20,
|
||||
color: cssVar('iconSecondary'),
|
||||
});
|
||||
export const message = style({
|
||||
fontSize: cssVar('fontSm'),
|
||||
textAlign: 'center',
|
||||
color: cssVar('black30'),
|
||||
userSelect: 'none',
|
||||
});
|
||||
|
||||
export const newButton = style({
|
||||
padding: '0 8px',
|
||||
height: '28px',
|
||||
fontSize: cssVar('fontXs'),
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import {
|
||||
type DropTargetDropEvent,
|
||||
type DropTargetOptions,
|
||||
useDropTarget,
|
||||
} from '@affine/component';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { FolderIcon } from '@blocksuite/icons/rc';
|
||||
|
||||
import { DropEffect, type ExplorerTreeNodeDropEffect } from '../../tree';
|
||||
import * as styles from './empty.css';
|
||||
|
||||
export const RootEmpty = ({
|
||||
onDrop,
|
||||
canDrop,
|
||||
dropEffect,
|
||||
}: {
|
||||
onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void;
|
||||
canDrop?: DropTargetOptions<AffineDNDData>['canDrop'];
|
||||
dropEffect?: ExplorerTreeNodeDropEffect;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
|
||||
const { dropTargetRef, draggedOverDraggable, draggedOverPosition } =
|
||||
useDropTarget<AffineDNDData>(
|
||||
() => ({
|
||||
data: {
|
||||
at: 'explorer:old-favorite:root',
|
||||
},
|
||||
onDrop: onDrop,
|
||||
canDrop: canDrop,
|
||||
}),
|
||||
[onDrop, canDrop]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.content} ref={dropTargetRef}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<FolderIcon className={styles.icon} />
|
||||
</div>
|
||||
<div
|
||||
data-testid="slider-bar-favorites-empty-message"
|
||||
className={styles.message}
|
||||
>
|
||||
{t['com.affine.rootAppSidebar.favorites.empty']()}
|
||||
</div>
|
||||
{dropEffect && draggedOverDraggable && (
|
||||
<DropEffect
|
||||
position={{
|
||||
x: draggedOverPosition.relativeX,
|
||||
y: draggedOverPosition.relativeY,
|
||||
}}
|
||||
dropEffect={dropEffect({
|
||||
source: draggedOverDraggable,
|
||||
treeInstruction: null,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,264 +0,0 @@
|
||||
import {
|
||||
type DropTargetDropEvent,
|
||||
type DropTargetOptions,
|
||||
IconButton,
|
||||
} from '@affine/component';
|
||||
import {
|
||||
type ExplorerTreeNodeDropEffect,
|
||||
ExplorerTreeRoot,
|
||||
} from '@affine/core/modules/explorer/views/tree';
|
||||
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||
import { ExplorerCollectionNode } from '../../nodes/collection';
|
||||
import { ExplorerDocNode } from '../../nodes/doc';
|
||||
import { RootEmpty } from './empty';
|
||||
|
||||
/**
|
||||
* @deprecated remove this after 0.17 released
|
||||
*/
|
||||
export const ExplorerOldFavorites = () => {
|
||||
const { favoriteItemsAdapter, docsService, workbenchService } = useServices({
|
||||
FavoriteItemsAdapter,
|
||||
DocsService,
|
||||
WorkbenchService,
|
||||
});
|
||||
|
||||
const docs = useLiveData(docsService.list.docs$);
|
||||
const trashDocs = useLiveData(docsService.list.trashDocs$);
|
||||
|
||||
const favorites = useLiveData(
|
||||
favoriteItemsAdapter.orderedFavorites$.map(favs => {
|
||||
return favs.filter(fav => {
|
||||
if (fav.type === 'doc') {
|
||||
return (
|
||||
docs.some(doc => doc.id === fav.id) &&
|
||||
!trashDocs.some(doc => doc.id === fav.id)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(data: DropTargetDropEvent<AffineDNDData>) => {
|
||||
if (
|
||||
data.source.data.entity?.type === 'doc' ||
|
||||
data.source.data.entity?.type === 'collection'
|
||||
) {
|
||||
favoriteItemsAdapter.set(
|
||||
data.source.data.entity.id,
|
||||
data.source.data.entity.type,
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
[favoriteItemsAdapter]
|
||||
);
|
||||
|
||||
const handleDropEffect = useCallback<ExplorerTreeNodeDropEffect>(data => {
|
||||
if (
|
||||
data.source.data.entity?.type === 'doc' ||
|
||||
data.source.data.entity?.type === 'collection'
|
||||
) {
|
||||
return 'link';
|
||||
}
|
||||
return;
|
||||
}, []);
|
||||
|
||||
const handleCanDrop = useMemo<DropTargetOptions<AffineDNDData>['canDrop']>(
|
||||
() => data => {
|
||||
return (
|
||||
data.source.data.entity?.type === 'doc' ||
|
||||
data.source.data.entity?.type === 'collection'
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleCreateNewFavoriteDoc = useCallback(() => {
|
||||
const newDoc = docsService.createDoc();
|
||||
favoriteItemsAdapter.set(newDoc.id, 'doc', true);
|
||||
workbenchService.workbench.openDoc(newDoc.id);
|
||||
}, [docsService, favoriteItemsAdapter, workbenchService]);
|
||||
|
||||
const handleOnChildrenDrop = useCallback(
|
||||
(
|
||||
favorite: { id: string; type: 'doc' | 'collection' },
|
||||
data: DropTargetDropEvent<AffineDNDData>
|
||||
) => {
|
||||
if (
|
||||
data.treeInstruction?.type === 'reorder-above' ||
|
||||
data.treeInstruction?.type === 'reorder-below'
|
||||
) {
|
||||
if (
|
||||
data.source.data.from?.at === 'explorer:old-favorite:list' &&
|
||||
(data.source.data.entity?.type === 'doc' ||
|
||||
data.source.data.entity?.type === 'collection')
|
||||
) {
|
||||
// is reordering
|
||||
favoriteItemsAdapter.sorter.moveTo(
|
||||
FavoriteItemsAdapter.getFavItemKey(
|
||||
data.source.data.entity.id,
|
||||
data.source.data.entity.type
|
||||
),
|
||||
FavoriteItemsAdapter.getFavItemKey(favorite.id, favorite.type),
|
||||
data.treeInstruction?.type === 'reorder-above' ? 'before' : 'after'
|
||||
);
|
||||
} else if (
|
||||
data.source.data.entity?.type === 'doc' ||
|
||||
data.source.data.entity?.type === 'collection'
|
||||
) {
|
||||
favoriteItemsAdapter.set(
|
||||
data.source.data.entity.id,
|
||||
data.source.data.entity?.type,
|
||||
true
|
||||
);
|
||||
favoriteItemsAdapter.sorter.moveTo(
|
||||
FavoriteItemsAdapter.getFavItemKey(
|
||||
data.source.data.entity.id,
|
||||
data.source.data.entity.type
|
||||
),
|
||||
FavoriteItemsAdapter.getFavItemKey(favorite.id, favorite.type),
|
||||
data.treeInstruction?.type === 'reorder-above' ? 'before' : 'after'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return; // not supported
|
||||
}
|
||||
},
|
||||
[favoriteItemsAdapter]
|
||||
);
|
||||
|
||||
const handleChildrenDropEffect = useCallback<ExplorerTreeNodeDropEffect>(
|
||||
data => {
|
||||
if (
|
||||
data.treeInstruction?.type === 'reorder-above' ||
|
||||
data.treeInstruction?.type === 'reorder-below'
|
||||
) {
|
||||
if (
|
||||
data.source.data.from?.at === 'explorer:old-favorite:list' &&
|
||||
(data.source.data.entity?.type === 'doc' ||
|
||||
data.source.data.entity?.type === 'collection')
|
||||
) {
|
||||
return 'move';
|
||||
} else if (
|
||||
data.source.data.entity?.type === 'doc' ||
|
||||
data.source.data.entity?.type === 'collection'
|
||||
) {
|
||||
return 'link';
|
||||
}
|
||||
}
|
||||
return; // not supported
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleChildrenCanDrop = useMemo<
|
||||
DropTargetOptions<AffineDNDData>['canDrop']
|
||||
>(
|
||||
() => args =>
|
||||
args.source.data.entity?.type === 'doc' ||
|
||||
args.source.data.entity?.type === 'collection',
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
name="favoritesOld"
|
||||
title={
|
||||
runtimeConfig.enableNewFavorite
|
||||
? `${t['com.affine.rootAppSidebar.favorites']()} (OLD)`
|
||||
: t['com.affine.rootAppSidebar.favorites']()
|
||||
}
|
||||
actions={
|
||||
<IconButton
|
||||
data-testid="explorer-bar-add-old-favorite-button"
|
||||
onClick={handleCreateNewFavoriteDoc}
|
||||
size="16"
|
||||
>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<ExplorerTreeRoot
|
||||
placeholder={
|
||||
<RootEmpty
|
||||
onDrop={handleDrop}
|
||||
canDrop={handleCanDrop}
|
||||
dropEffect={handleDropEffect}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{favorites.map((favorite, i) => (
|
||||
<ExplorerFavoriteNode
|
||||
key={favorite.id + ':' + i}
|
||||
favorite={favorite}
|
||||
onDrop={handleOnChildrenDrop}
|
||||
dropEffect={handleChildrenDropEffect}
|
||||
canDrop={handleChildrenCanDrop}
|
||||
/>
|
||||
))}
|
||||
</ExplorerTreeRoot>
|
||||
</CollapsibleSection>
|
||||
);
|
||||
};
|
||||
|
||||
const childLocation = {
|
||||
at: 'explorer:old-favorite:list' as const,
|
||||
};
|
||||
const ExplorerFavoriteNode = ({
|
||||
favorite,
|
||||
onDrop,
|
||||
canDrop,
|
||||
dropEffect,
|
||||
}: {
|
||||
favorite: {
|
||||
id: string;
|
||||
type: 'collection' | 'doc';
|
||||
};
|
||||
canDrop?: DropTargetOptions<AffineDNDData>['canDrop'];
|
||||
onDrop: (
|
||||
favorite: {
|
||||
id: string;
|
||||
type: 'collection' | 'doc';
|
||||
},
|
||||
data: DropTargetDropEvent<AffineDNDData>
|
||||
) => void;
|
||||
dropEffect: ExplorerTreeNodeDropEffect;
|
||||
}) => {
|
||||
const handleOnChildrenDrop = useCallback(
|
||||
(data: DropTargetDropEvent<AffineDNDData>) => {
|
||||
onDrop(favorite, data);
|
||||
},
|
||||
[favorite, onDrop]
|
||||
);
|
||||
return favorite.type === 'doc' ? (
|
||||
<ExplorerDocNode
|
||||
key={favorite.id}
|
||||
docId={favorite.id}
|
||||
location={childLocation}
|
||||
onDrop={handleOnChildrenDrop}
|
||||
dropEffect={dropEffect}
|
||||
canDrop={canDrop}
|
||||
/>
|
||||
) : (
|
||||
<ExplorerCollectionNode
|
||||
key={favorite.id}
|
||||
collectionId={favorite.id}
|
||||
location={childLocation}
|
||||
onDrop={handleOnChildrenDrop}
|
||||
dropEffect={dropEffect}
|
||||
canDrop={canDrop}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
export {
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
FavoriteItemsAdapter,
|
||||
MigrationFavoriteItemsAdapter as FavoriteItemsAdapter,
|
||||
WorkspacePropertiesAdapter,
|
||||
} from './services/adapter';
|
||||
export { WorkspaceLegacyProperties } from './services/legacy-properties';
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { FavoriteService } from '../favorite';
|
||||
import {
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
FavoriteItemsAdapter,
|
||||
MigrationFavoriteItemsAdapter,
|
||||
WorkspacePropertiesAdapter,
|
||||
} from './services/adapter';
|
||||
import { WorkspaceLegacyProperties } from './services/legacy-properties';
|
||||
@@ -24,9 +24,6 @@ export function configureWorkspacePropertiesModule(framework: Framework) {
|
||||
.scope(WorkspaceScope)
|
||||
.service(WorkspaceLegacyProperties, [WorkspaceService])
|
||||
.service(WorkspacePropertiesAdapter, [WorkspaceService])
|
||||
.service(FavoriteItemsAdapter, [WorkspacePropertiesAdapter])
|
||||
.service(CompatibleFavoriteItemsAdapter, [
|
||||
FavoriteItemsAdapter,
|
||||
FavoriteService,
|
||||
]);
|
||||
.service(MigrationFavoriteItemsAdapter, [WorkspacePropertiesAdapter])
|
||||
.service(CompatibleFavoriteItemsAdapter, [FavoriteService]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// the adapter is to bridge the workspace rootdoc & native js bindings
|
||||
import { createFractionalIndexingSortableHelper } from '@affine/core/utils';
|
||||
import { createYProxy, type Y } from '@blocksuite/store';
|
||||
import type { WorkspaceService } from '@toeverything/infra';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
PagePropertyType,
|
||||
PageSystemPropertyId,
|
||||
type WorkspaceAffineProperties,
|
||||
type WorkspaceFavoriteItem,
|
||||
} from './schema';
|
||||
|
||||
const AFFINE_PROPERTIES_ID = 'affine:workspace-properties';
|
||||
@@ -93,7 +91,6 @@ export class WorkspacePropertiesAdapter extends Service {
|
||||
},
|
||||
},
|
||||
},
|
||||
favorites: {},
|
||||
pageProperties: {},
|
||||
});
|
||||
}
|
||||
@@ -167,211 +164,65 @@ export class WorkspacePropertiesAdapter extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use CompatibleFavoriteItemsAdapter
|
||||
*/
|
||||
export class FavoriteItemsAdapter extends Service {
|
||||
export class MigrationFavoriteItemsAdapter extends Service {
|
||||
constructor(private readonly adapter: WorkspacePropertiesAdapter) {
|
||||
super();
|
||||
this.migrateFavorites();
|
||||
}
|
||||
|
||||
readonly sorter = createFractionalIndexingSortableHelper<
|
||||
WorkspaceFavoriteItem,
|
||||
string
|
||||
>(this);
|
||||
|
||||
static getFavItemKey(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||
return `${type}:${id}`;
|
||||
}
|
||||
|
||||
favorites$ = this.adapter.properties$.map(() =>
|
||||
this.getItems().filter(i => i.value)
|
||||
);
|
||||
|
||||
orderedFavorites$ = this.adapter.properties$.map(() => {
|
||||
const seen = new Set<string>();
|
||||
return this.sorter.getOrderedItems().filter(item => {
|
||||
const key = FavoriteItemsAdapter.getFavItemKey(item.id, item.type);
|
||||
if (seen.has(key) || !item.value) {
|
||||
return null;
|
||||
}
|
||||
seen.add(key);
|
||||
return item;
|
||||
});
|
||||
});
|
||||
|
||||
getItems() {
|
||||
return Object.entries(this.adapter.favorites ?? {})
|
||||
.filter(([k]) => k.includes(':'))
|
||||
.map(([, v]) => v);
|
||||
}
|
||||
|
||||
get favorites() {
|
||||
return this.adapter.favorites;
|
||||
}
|
||||
|
||||
get workspace() {
|
||||
return this.adapter.workspaceService.workspace;
|
||||
}
|
||||
|
||||
getItemId(item: WorkspaceFavoriteItem) {
|
||||
return FavoriteItemsAdapter.getFavItemKey(item.id, item.type);
|
||||
}
|
||||
|
||||
getItemOrder(item: WorkspaceFavoriteItem) {
|
||||
return item.order;
|
||||
}
|
||||
|
||||
setItemOrder(item: WorkspaceFavoriteItem, order: string) {
|
||||
item.order = order;
|
||||
}
|
||||
|
||||
// read from the workspace meta and migrate to the properties
|
||||
private migrateFavorites() {
|
||||
// only migrate if favorites is empty
|
||||
if (Object.keys(this.favorites ?? {}).length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// old favorited pages
|
||||
const oldFavorites = this.workspace.docCollection.meta.docMetas
|
||||
.filter(meta => meta.favorite)
|
||||
.map(meta => meta.id);
|
||||
|
||||
this.adapter.transact(() => {
|
||||
for (const id of oldFavorites) {
|
||||
this.set(id, 'doc', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isFavorite(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||
const existing = this.getFavoriteItem(id, type);
|
||||
return existing?.value ?? false;
|
||||
}
|
||||
|
||||
isFavorite$(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||
return this.favorites$.map(() => {
|
||||
return this.isFavorite(id, type);
|
||||
});
|
||||
}
|
||||
|
||||
private getFavoriteItem(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||
return this.favorites?.[FavoriteItemsAdapter.getFavItemKey(id, type)];
|
||||
}
|
||||
|
||||
// add or set a new fav item to the list. note the id added with prefix
|
||||
set(
|
||||
id: string,
|
||||
type: WorkspaceFavoriteItem['type'],
|
||||
value: boolean,
|
||||
order?: string
|
||||
) {
|
||||
this.adapter.ensureRootProperties();
|
||||
if (!this.favorites) {
|
||||
throw new Error('Favorites is not initialized');
|
||||
}
|
||||
const existing = this.getFavoriteItem(id, type);
|
||||
if (!existing) {
|
||||
this.favorites[FavoriteItemsAdapter.getFavItemKey(id, type)] = {
|
||||
id,
|
||||
type,
|
||||
value: true,
|
||||
order: order ?? this.sorter.getNewItemOrder(),
|
||||
};
|
||||
} else {
|
||||
Object.assign(existing, {
|
||||
value,
|
||||
order: order ?? existing.order,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggle(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||
this.set(id, type, !this.isFavorite(id, type));
|
||||
}
|
||||
|
||||
remove(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||
this.adapter.ensureRootProperties();
|
||||
const existing = this.getFavoriteItem(id, type);
|
||||
if (existing) {
|
||||
existing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
clearAll() {
|
||||
this.adapter.cleanupFavorites();
|
||||
}
|
||||
}
|
||||
|
||||
type CompatibleFavoriteSupportType =
|
||||
| WorkspaceFavoriteItem['type']
|
||||
| FavoriteSupportType;
|
||||
type CompatibleFavoriteSupportType = FavoriteSupportType;
|
||||
|
||||
/**
|
||||
* A service written for compatibility,with the same API as FavoriteItemsAdapter.
|
||||
* When `runtimeConfig.enableNewFavorite` is false, it operates FavoriteItemsAdapter,
|
||||
* and when it is true, it operates FavoriteService.
|
||||
* A service written for compatibility,with the same API as old FavoriteItemsAdapter.
|
||||
*/
|
||||
export class CompatibleFavoriteItemsAdapter extends Service {
|
||||
constructor(
|
||||
private readonly favoriteItemsAdapter: FavoriteItemsAdapter,
|
||||
private readonly favoriteService: FavoriteService
|
||||
) {
|
||||
constructor(private readonly favoriteService: FavoriteService) {
|
||||
super();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
this.favoriteService.favoriteList.toggle(type, id);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return this.favoriteService.favoriteList.isFavorite$(type, id);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return this.favoriteService.favoriteList.isFavorite$(type, id).value;
|
||||
}
|
||||
|
||||
get favorites$() {
|
||||
if (runtimeConfig.enableNewFavorite) {
|
||||
return this.favoriteService.favoriteList.list$.map<
|
||||
{
|
||||
id: string;
|
||||
order: string;
|
||||
type: 'doc' | 'collection';
|
||||
value: boolean;
|
||||
}[]
|
||||
>(v =>
|
||||
v
|
||||
.filter(i => i.type === 'doc' || i.type === 'collection') // only support doc and collection
|
||||
.map(i => ({
|
||||
id: i.id,
|
||||
order: '',
|
||||
type: i.type as 'doc' | 'collection',
|
||||
value: true,
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
return this.favoriteItemsAdapter.favorites$;
|
||||
}
|
||||
return this.favoriteService.favoriteList.list$.map<
|
||||
{
|
||||
id: string;
|
||||
order: string;
|
||||
type: 'doc' | 'collection';
|
||||
value: boolean;
|
||||
}[]
|
||||
>(v =>
|
||||
v
|
||||
.filter(i => i.type === 'doc' || i.type === 'collection') // only support doc and collection
|
||||
.map(i => ({
|
||||
id: i.id,
|
||||
order: '',
|
||||
type: i.type as 'doc' | 'collection',
|
||||
value: true,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
allowLocalWorkspace:
|
||||
buildFlags.distribution === 'desktop' ? true : false,
|
||||
enableOrganize: true,
|
||||
enableNewFavorite: true,
|
||||
enableOldFavorite: false,
|
||||
|
||||
enableInfoModal: true,
|
||||
|
||||
// CAUTION(@forehalo): product not ready, do not enable it
|
||||
@@ -57,8 +54,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||
enableInfoModal: true,
|
||||
enableOrganize: true,
|
||||
enableNewFavorite: true,
|
||||
enableOldFavorite: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user