mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 13:25:12 +00:00
refactor(core): favorite adapter (#6285)
1. abstraction over favourites that supports different type of resources 2. sorting abstraction
This commit is contained in:
@@ -18,6 +18,7 @@ import { TagService } from './tag';
|
||||
import { Workbench } from './workbench';
|
||||
import {
|
||||
CurrentWorkspaceService,
|
||||
FavoriteItemsAdapter,
|
||||
WorkspaceLegacyProperties,
|
||||
WorkspacePropertiesAdapter,
|
||||
} from './workspace';
|
||||
@@ -30,6 +31,7 @@ export function configureBusinessServices(services: ServiceCollection) {
|
||||
.add(Navigator, [Workbench])
|
||||
.add(RightSidebar, [GlobalState])
|
||||
.add(WorkspacePropertiesAdapter, [Workspace])
|
||||
.add(FavoriteItemsAdapter, [WorkspacePropertiesAdapter])
|
||||
.add(CollectionService, [Workspace])
|
||||
.add(WorkspaceLegacyProperties, [Workspace])
|
||||
.add(TagService, [WorkspaceLegacyProperties, PageRecordList]);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// the adapter is to bridge the workspace rootdoc & native js bindings
|
||||
|
||||
import type { Y } from '@blocksuite/store';
|
||||
import { createYProxy } from '@blocksuite/store';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { createFractionalIndexingSortableHelper } from '@affine/core/utils';
|
||||
import { createYProxy, type Y } from '@blocksuite/store';
|
||||
import { LiveData, type Workspace } from '@toeverything/infra';
|
||||
import { defaultsDeep } from 'lodash-es';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import type {
|
||||
WorkspaceAffineProperties,
|
||||
WorkspaceFavoriteItem,
|
||||
import {
|
||||
PagePropertyType,
|
||||
PageSystemPropertyId,
|
||||
type WorkspaceAffineProperties,
|
||||
type WorkspaceFavoriteItem,
|
||||
} from './schema';
|
||||
import { PagePropertyType, PageSystemPropertyId } from './schema';
|
||||
|
||||
const AFFINE_PROPERTIES_ID = 'affine:workspace-properties';
|
||||
|
||||
@@ -27,6 +28,7 @@ export class WorkspacePropertiesAdapter {
|
||||
// provides a easy-to-use interface for workspace properties
|
||||
public readonly proxy: WorkspaceAffineProperties;
|
||||
public readonly properties: Y.Map<any>;
|
||||
public readonly properties$: LiveData<WorkspaceAffineProperties>;
|
||||
|
||||
private ensuredRoot = false;
|
||||
private ensuredPages = {} as Record<string, boolean>;
|
||||
@@ -36,9 +38,25 @@ export class WorkspacePropertiesAdapter {
|
||||
const rootDoc = workspace.docCollection.doc;
|
||||
this.properties = rootDoc.getMap(AFFINE_PROPERTIES_ID);
|
||||
this.proxy = createYProxy(this.properties);
|
||||
|
||||
this.properties$ = LiveData.from(
|
||||
new Observable(observer => {
|
||||
const update = () => {
|
||||
requestAnimationFrame(() => {
|
||||
observer.next(new Proxy(this.proxy, {}));
|
||||
});
|
||||
};
|
||||
update();
|
||||
this.properties.observeDeep(update);
|
||||
return () => {
|
||||
this.properties.unobserveDeep(update);
|
||||
};
|
||||
}),
|
||||
this.proxy
|
||||
);
|
||||
}
|
||||
|
||||
private ensureRootProperties() {
|
||||
public ensureRootProperties() {
|
||||
if (this.ensuredRoot) {
|
||||
return;
|
||||
}
|
||||
@@ -120,10 +138,6 @@ export class WorkspacePropertiesAdapter {
|
||||
return this.pageProperties?.[pageId] ?? null;
|
||||
}
|
||||
|
||||
isFavorite(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||
return this.favorites?.[id]?.type === type;
|
||||
}
|
||||
|
||||
getJournalPageDateString(id: string) {
|
||||
return this.pageProperties?.[id]?.system[PageSystemPropertyId.Journal]
|
||||
?.value;
|
||||
@@ -135,3 +149,119 @@ export class WorkspacePropertiesAdapter {
|
||||
pageProperties!.system[PageSystemPropertyId.Journal].value = date;
|
||||
}
|
||||
}
|
||||
|
||||
export class FavoriteItemsAdapter {
|
||||
constructor(private readonly adapter: WorkspacePropertiesAdapter) {
|
||||
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)
|
||||
);
|
||||
|
||||
getItems() {
|
||||
return Object.values(this.adapter.favorites ?? {});
|
||||
}
|
||||
|
||||
get favorites() {
|
||||
return this.adapter.favorites;
|
||||
}
|
||||
|
||||
get workspace() {
|
||||
return this.adapter.workspace;
|
||||
}
|
||||
|
||||
getItemId(item: WorkspaceFavoriteItem) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,9 @@ export type PageInfoTagsItem = z.infer<typeof PageInfoTagsItemSchema>;
|
||||
// ====== workspace properties schema ======
|
||||
export const WorkspaceFavoriteItemSchema = z.object({
|
||||
id: z.string(),
|
||||
order: z.number(),
|
||||
type: z.enum(['page', 'collection']),
|
||||
order: z.string(),
|
||||
type: z.enum(['doc', 'collection']),
|
||||
value: z.boolean(),
|
||||
});
|
||||
|
||||
export type WorkspaceFavoriteItem = z.infer<typeof WorkspaceFavoriteItemSchema>;
|
||||
|
||||
Reference in New Issue
Block a user