7
packages/common/env/src/global.ts
vendored
7
packages/common/env/src/global.ts
vendored
@@ -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>;
|
||||||
|
|||||||
@@ -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')
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 = ({
|
||||||
|
|||||||
@@ -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']()} />
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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" />,
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 {
|
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,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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$;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user