chore(core): remove old favorite (#7743)

closes AF-1203
This commit is contained in:
EYHN
2024-08-07 02:19:53 +00:00
parent c2cf331ff7
commit dd74cfea14
15 changed files with 92 additions and 631 deletions

View File

@@ -31,13 +31,6 @@ export const runtimeFlagsSchema = z.object({
enableExperimentalFeature: z.boolean(), enableExperimentalFeature: z.boolean(),
enableInfoModal: z.boolean(), enableInfoModal: z.boolean(),
enableOrganize: 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>; export type RuntimeConfig = z.infer<typeof runtimeFlagsSchema>;

View File

@@ -1,6 +1,6 @@
import { toast } from '@affine/component'; import { toast } from '@affine/component';
import { CollectionService } from '@affine/core/modules/collection'; 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 { useI18n } from '@affine/i18n';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
@@ -16,7 +16,7 @@ import { VirtualizedList } from '../virtualized-list';
const FavoriteOperation = ({ collection }: { collection: ListItem }) => { const FavoriteOperation = ({ collection }: { collection: ListItem }) => {
const t = useI18n(); const t = useI18n();
const favAdapter = useService(FavoriteItemsAdapter); const favAdapter = useService(CompatibleFavoriteItemsAdapter);
const isFavorite = useLiveData( const isFavorite = useLiveData(
favAdapter.isFavorite$(collection.id, 'collection') favAdapter.isFavorite$(collection.id, 'collection')
); );

View File

@@ -211,16 +211,14 @@ export const PageOperationCell = ({
); );
return ( return (
<> <>
{runtimeConfig.enableNewFavorite && ( <ColWrapper
<ColWrapper hideInSmallContainer
hideInSmallContainer data-testid="page-list-item-favorite"
data-testid="page-list-item-favorite" data-favorite={favourite ? true : undefined}
data-favorite={favourite ? true : undefined} className={styles.favoriteCell}
className={styles.favoriteCell} >
> <FavoriteTag onClick={onToggleFavoritePage} active={favourite} />
<FavoriteTag onClick={onToggleFavoritePage} active={favourite} /> </ColWrapper>
</ColWrapper>
)}
<ColWrapper alignment="start"> <ColWrapper alignment="start">
<Menu <Menu
items={OperationMenu} items={OperationMenu}

View File

@@ -1,8 +1,11 @@
import { toast } from '@affine/component';
import { FavoriteService } from '@affine/core/modules/favorite';
import { TagService } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { FavoriteTag } from '../components/favorite-tag';
import { tagHeaderColsDef } from '../header-col-def'; import { tagHeaderColsDef } from '../header-col-def';
import { TagListItemRenderer } from '../page-group'; import { TagListItemRenderer } from '../page-group';
import { ListTableHeader } from '../page-header'; import { ListTableHeader } from '../page-header';
@@ -11,32 +14,29 @@ import { SelectorLayout } from '../selector/selector-layout';
import type { ListItem, TagMeta } from '../types'; import type { ListItem, TagMeta } from '../types';
import { VirtualizedList } from '../virtualized-list'; import { VirtualizedList } from '../virtualized-list';
// TODO(@EYHN): add tag to favourite support const FavoriteOperation = ({ tag }: { tag: ListItem }) => {
const FavoriteOperation = ({ tag: _ }: { tag: ListItem }) => { const t = useI18n();
// const t = useI18n(); const favoriteService = useService(FavoriteService);
// const favAdapter = useService(FavoriteItemsAdapter); const isFavorite = useLiveData(
// const isFavorite = useLiveData( favoriteService.favoriteList.isFavorite$('tag', tag.id)
// favAdapter.isFavorite$(tag.id, 'tag') );
// );
// const onToggleFavoriteCollection = useCallback(() => { const onToggleFavoriteCollection = useCallback(() => {
// favAdapter.toggle(tag.id, 'tag'); favoriteService.favoriteList.toggle('tag', tag.id);
// toast( toast(
// isFavorite isFavorite
// ? t['com.affine.toastMessage.removedFavorites']() ? t['com.affine.toastMessage.removedFavorites']()
// : t['com.affine.toastMessage.addedFavorites']() : t['com.affine.toastMessage.addedFavorites']()
// ); );
// }, [tag.id, favAdapter, isFavorite, t]); }, [favoriteService.favoriteList, tag.id, isFavorite, t]);
// return ( return (
// <FavoriteTag <FavoriteTag
// style={{ marginRight: 8 }} style={{ marginRight: 8 }}
// onClick={onToggleFavoriteCollection} onClick={onToggleFavoriteCollection}
// active={isFavorite} active={isFavorite}
// /> />
// ); );
return null;
}; };
export const SelectTag = ({ export const SelectTag = ({

View File

@@ -5,7 +5,6 @@ import {
ExplorerCollections, ExplorerCollections,
ExplorerFavorites, ExplorerFavorites,
ExplorerMigrationFavorites, ExplorerMigrationFavorites,
ExplorerOldFavorites,
ExplorerOrganize, ExplorerOrganize,
} from '@affine/core/modules/explorer'; } from '@affine/core/modules/explorer';
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags'; import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
@@ -169,10 +168,9 @@ export const RootAppSidebar = (): ReactElement => {
</MenuItem> </MenuItem>
</SidebarContainer> </SidebarContainer>
<SidebarScrollableContainer> <SidebarScrollableContainer>
{runtimeConfig.enableNewFavorite && <ExplorerFavorites />} <ExplorerFavorites />
{runtimeConfig.enableOrganize && <ExplorerOrganize />} {runtimeConfig.enableOrganize && <ExplorerOrganize />}
{runtimeConfig.enableNewFavorite && <ExplorerMigrationFavorites />} <ExplorerMigrationFavorites />
{runtimeConfig.enableOldFavorite && <ExplorerOldFavorites />}
<ExplorerCollections /> <ExplorerCollections />
<ExplorerTags /> <ExplorerTags />
<CategoryDivider label={t['com.affine.rootAppSidebar.others']()} /> <CategoryDivider label={t['com.affine.rootAppSidebar.others']()} />

View File

@@ -11,7 +11,6 @@ export type { CollapsibleSectionName } from './types';
export { ExplorerCollections } from './views/sections/collections'; export { ExplorerCollections } from './views/sections/collections';
export { ExplorerFavorites } from './views/sections/favorites'; export { ExplorerFavorites } from './views/sections/favorites';
export { ExplorerMigrationFavorites } from './views/sections/migration-favorites'; export { ExplorerMigrationFavorites } from './views/sections/migration-favorites';
export { ExplorerOldFavorites } from './views/sections/old-favorites';
export { ExplorerOrganize } from './views/sections/organize'; export { ExplorerOrganize } from './views/sections/organize';
export function configureExplorerModule(framework: Framework) { export function configureExplorerModule(framework: Framework) {

View File

@@ -675,15 +675,10 @@ export const ExplorerFolderNodeFolder = ({
), ),
}, },
// favorite, only new favorite available {
...(runtimeConfig.enableNewFavorite && node.id index: 200,
? [ view: node.id ? <FavoriteFolderOperation id={node.id} /> : null,
{ },
index: 200,
view: <FavoriteFolderOperation id={node.id} />,
},
]
: []),
{ {
index: 9999, index: 9999,

View File

@@ -133,33 +133,29 @@ export const useExplorerTagNodeOperations = (
}, },
] ]
: []), : []),
...(runtimeConfig.enableNewFavorite {
? [ index: 199,
{ view: (
index: 199, <MenuItem
view: ( preFix={
<MenuItem <MenuIcon>
preFix={ {favorite ? (
<MenuIcon> <FavoritedIcon
{favorite ? ( style={{ color: 'var(--affine-primary-color)' }}
<FavoritedIcon />
style={{ color: 'var(--affine-primary-color)' }} ) : (
/> <FavoriteIcon />
) : ( )}
<FavoriteIcon /> </MenuIcon>
)} }
</MenuIcon> onClick={handleToggleFavoriteTag}
} >
onClick={handleToggleFavoriteTag} {favorite
> ? t['com.affine.favoritePageOperation.remove']()
{favorite : t['com.affine.favoritePageOperation.add']()}
? t['com.affine.favoritePageOperation.remove']() </MenuItem>
: t['com.affine.favoritePageOperation.add']()} ),
</MenuItem> },
),
},
]
: []),
{ {
index: 9999, index: 9999,
view: <MenuSeparator key="menu-separator" />, view: <MenuSeparator key="menu-separator" />,

View File

@@ -25,7 +25,7 @@ export const ExplorerMigrationFavorites = () => {
const { openConfirmModal } = useConfirmModal(); const { openConfirmModal } = useConfirmModal();
const favorites = useLiveData( const favorites = useLiveData(
favoriteItemsAdapter.orderedFavorites$.map(favs => { favoriteItemsAdapter.favorites$.map(favs => {
return favs.filter(fav => { return favs.filter(fav => {
if (fav.type === 'doc') { if (fav.type === 'doc') {
return ( return (

View File

@@ -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'),
});

View File

@@ -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>
);
};

View File

@@ -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}
/>
);
};

View File

@@ -1,6 +1,6 @@
export { export {
CompatibleFavoriteItemsAdapter, CompatibleFavoriteItemsAdapter,
FavoriteItemsAdapter, MigrationFavoriteItemsAdapter as FavoriteItemsAdapter,
WorkspacePropertiesAdapter, WorkspacePropertiesAdapter,
} from './services/adapter'; } from './services/adapter';
export { WorkspaceLegacyProperties } from './services/legacy-properties'; export { WorkspaceLegacyProperties } from './services/legacy-properties';
@@ -14,7 +14,7 @@ import {
import { FavoriteService } from '../favorite'; import { FavoriteService } from '../favorite';
import { import {
CompatibleFavoriteItemsAdapter, CompatibleFavoriteItemsAdapter,
FavoriteItemsAdapter, MigrationFavoriteItemsAdapter,
WorkspacePropertiesAdapter, WorkspacePropertiesAdapter,
} from './services/adapter'; } from './services/adapter';
import { WorkspaceLegacyProperties } from './services/legacy-properties'; import { WorkspaceLegacyProperties } from './services/legacy-properties';
@@ -24,9 +24,6 @@ export function configureWorkspacePropertiesModule(framework: Framework) {
.scope(WorkspaceScope) .scope(WorkspaceScope)
.service(WorkspaceLegacyProperties, [WorkspaceService]) .service(WorkspaceLegacyProperties, [WorkspaceService])
.service(WorkspacePropertiesAdapter, [WorkspaceService]) .service(WorkspacePropertiesAdapter, [WorkspaceService])
.service(FavoriteItemsAdapter, [WorkspacePropertiesAdapter]) .service(MigrationFavoriteItemsAdapter, [WorkspacePropertiesAdapter])
.service(CompatibleFavoriteItemsAdapter, [ .service(CompatibleFavoriteItemsAdapter, [FavoriteService]);
FavoriteItemsAdapter,
FavoriteService,
]);
} }

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
// the adapter is to bridge the workspace rootdoc & native js bindings // 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 { createYProxy, type Y } from '@blocksuite/store';
import type { WorkspaceService } from '@toeverything/infra'; import type { WorkspaceService } from '@toeverything/infra';
import { LiveData, Service } from '@toeverything/infra'; import { LiveData, Service } from '@toeverything/infra';
@@ -12,7 +11,6 @@ import {
PagePropertyType, PagePropertyType,
PageSystemPropertyId, PageSystemPropertyId,
type WorkspaceAffineProperties, type WorkspaceAffineProperties,
type WorkspaceFavoriteItem,
} from './schema'; } from './schema';
const AFFINE_PROPERTIES_ID = 'affine:workspace-properties'; const AFFINE_PROPERTIES_ID = 'affine:workspace-properties';
@@ -93,7 +91,6 @@ export class WorkspacePropertiesAdapter extends Service {
}, },
}, },
}, },
favorites: {},
pageProperties: {}, pageProperties: {},
}); });
} }
@@ -167,211 +164,65 @@ export class WorkspacePropertiesAdapter extends Service {
} }
} }
/** export class MigrationFavoriteItemsAdapter extends Service {
* @deprecated use CompatibleFavoriteItemsAdapter
*/
export class FavoriteItemsAdapter extends Service {
constructor(private readonly adapter: WorkspacePropertiesAdapter) { constructor(private readonly adapter: WorkspacePropertiesAdapter) {
super(); super();
this.migrateFavorites();
}
readonly sorter = createFractionalIndexingSortableHelper<
WorkspaceFavoriteItem,
string
>(this);
static getFavItemKey(id: string, type: WorkspaceFavoriteItem['type']) {
return `${type}:${id}`;
} }
favorites$ = this.adapter.properties$.map(() => favorites$ = this.adapter.properties$.map(() =>
this.getItems().filter(i => i.value) 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() { getItems() {
return Object.entries(this.adapter.favorites ?? {}) return Object.entries(this.adapter.favorites ?? {})
.filter(([k]) => k.includes(':')) .filter(([k]) => k.includes(':'))
.map(([, v]) => v); .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() { clearAll() {
this.adapter.cleanupFavorites(); this.adapter.cleanupFavorites();
} }
} }
type CompatibleFavoriteSupportType = type CompatibleFavoriteSupportType = FavoriteSupportType;
| WorkspaceFavoriteItem['type']
| FavoriteSupportType;
/** /**
* A service written for compatibility,with the same API as FavoriteItemsAdapter. * A service written for compatibility,with the same API as old FavoriteItemsAdapter.
* When `runtimeConfig.enableNewFavorite` is false, it operates FavoriteItemsAdapter,
* and when it is true, it operates FavoriteService.
*/ */
export class CompatibleFavoriteItemsAdapter extends Service { export class CompatibleFavoriteItemsAdapter extends Service {
constructor( constructor(private readonly favoriteService: FavoriteService) {
private readonly favoriteItemsAdapter: FavoriteItemsAdapter,
private readonly favoriteService: FavoriteService
) {
super(); super();
} }
// old adapter only supports doc and collection
private isNewType(type: CompatibleFavoriteSupportType) {
return type !== 'doc' && type !== 'collection';
}
toggle(id: string, type: CompatibleFavoriteSupportType) { toggle(id: string, type: CompatibleFavoriteSupportType) {
if (runtimeConfig.enableNewFavorite || this.isNewType(type)) { this.favoriteService.favoriteList.toggle(type, id);
this.favoriteService.favoriteList.toggle(type, id);
} else {
this.favoriteItemsAdapter.toggle(id, type);
}
} }
isFavorite$(id: string, type: CompatibleFavoriteSupportType) { isFavorite$(id: string, type: CompatibleFavoriteSupportType) {
if (runtimeConfig.enableNewFavorite || this.isNewType(type)) { return this.favoriteService.favoriteList.isFavorite$(type, id);
return this.favoriteService.favoriteList.isFavorite$(type, id);
} else {
return this.favoriteItemsAdapter.isFavorite$(id, type);
}
} }
isFavorite(id: string, type: CompatibleFavoriteSupportType) { isFavorite(id: string, type: CompatibleFavoriteSupportType) {
if (runtimeConfig.enableNewFavorite || this.isNewType(type)) { return this.favoriteService.favoriteList.isFavorite$(type, id).value;
return this.favoriteService.favoriteList.isFavorite$(type, id).value;
} else {
return this.favoriteItemsAdapter.isFavorite(id, type);
}
} }
get favorites$() { get favorites$() {
if (runtimeConfig.enableNewFavorite) { return this.favoriteService.favoriteList.list$.map<
return this.favoriteService.favoriteList.list$.map< {
{ id: string;
id: string; order: string;
order: string; type: 'doc' | 'collection';
type: 'doc' | 'collection'; value: boolean;
value: boolean; }[]
}[] >(v =>
>(v => v
v .filter(i => i.type === 'doc' || i.type === 'collection') // only support doc and collection
.filter(i => i.type === 'doc' || i.type === 'collection') // only support doc and collection .map(i => ({
.map(i => ({ id: i.id,
id: i.id, order: '',
order: '', type: i.type as 'doc' | 'collection',
type: i.type as 'doc' | 'collection', value: true,
value: true, }))
})) );
);
} else {
return this.favoriteItemsAdapter.favorites$;
}
} }
} }

View File

@@ -22,9 +22,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
allowLocalWorkspace: allowLocalWorkspace:
buildFlags.distribution === 'desktop' ? true : false, buildFlags.distribution === 'desktop' ? true : false,
enableOrganize: true, enableOrganize: true,
enableNewFavorite: true,
enableOldFavorite: false,
enableInfoModal: true, enableInfoModal: true,
// CAUTION(@forehalo): product not ready, do not enable it // 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', changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
enableInfoModal: true, enableInfoModal: true,
enableOrganize: true, enableOrganize: true,
enableNewFavorite: true,
enableOldFavorite: true,
}; };
}, },
}; };