mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 09:36:17 +08:00
feat(core): add collection rules module (#11683)
whats changed:
### orm
add a new `select$` method, can subscribe on only one field to improve batch subscribe performance
### yjs-observable
add a new `yjsObservePath` method, which can subscribe to changes from specific path in yjs. Improves batch subscribe performance
```ts
yjsGetPath(
this.workspaceService.workspace.rootYDoc.getMap('meta'),
'pages'
).pipe(
switchMap(pages => yjsObservePath(pages, '*.tags')),
map(pages => {
// only when tags changed
})
)
```
### standard property naming
All `DocProperty` components renamed to `WorkspaceProperty` which is consistent with the product definition.
### `WorkspacePropertyService`
Split the workspace property management logic from the `doc` module and create a new `WorkspacePropertyService`. The new service manages the creation and modification of properties, and the `docService` is only responsible for storing the property value data.
### new `<Filters />` component
in `core/component/filter`
### new `<ExplorerDisplayMenuButton />` component
in `core/component/explorer/display-menu`

### new `/workspace/xxx/all-new` route
New route for test components and functions
### new collection role service
Implemented some filter group order rules
see `collection-rules/index.ts`
### standard property type definition
define type in `modules\workspace-property\types.ts`

define components (name,icon,....) in `components\workspace-property-types\index.ts`

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
- **New Features**
- Introduced comprehensive filtering, grouping, and ordering capabilities for workspace documents with reactive updates.
- Added a new "All Pages" workspace view supporting dynamic filters and display preferences.
- Developed UI components for filter creation, condition editing, and display menu controls.
- Launched enhanced tag management with inline editors, selection, creation, and deletion workflows.
- Added workspace property types with dedicated filter UIs including checkbox, date, tags, and text.
- Introduced workspace property management replacing document property handling.
- Added modular providers for filters, group-by, and order-by operations supporting various property types and system attributes.
- **Improvements**
- Standardized tag and property naming conventions across the application (using `name` instead of `value` or `title`).
- Migrated document property handling to workspace property-centric logic.
- Enhanced internationalization with additional filter and display menu labels.
- Improved styling for filter conditions, display menus, and workspace pages.
- Optimized reactive data subscriptions and state management for performance.
- Refined schema typings and type safety for workspace properties.
- Updated imports and component references to workspace property equivalents throughout frontend.
- **Bug Fixes**
- Resolved tag property inconsistencies affecting display and filtering.
- Fixed filter and tag selection behaviors for accurate and reliable UI interactions.
- **Chores**
- Added and refined test cases for ORM, observables, and filtering logic.
- Cleaned up legacy document property code and improved type safety.
- Modularized and restructured components for better maintainability.
- Introduced new CSS styles for workspace pages and display menus.
- Added framework module configurations for collection rules and workspace property features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -15,6 +15,7 @@ export { FetchService } from './services/fetch';
|
||||
export { GraphQLService } from './services/graphql';
|
||||
export { InvitationService } from './services/invitation';
|
||||
export { InvoicesService } from './services/invoices';
|
||||
export type { PublicUserInfo } from './services/public-user';
|
||||
export { PublicUserService } from './services/public-user';
|
||||
export { SelfhostGenerateLicenseService } from './services/selfhost-generate-license';
|
||||
export { SelfhostLicenseService } from './services/selfhost-license';
|
||||
|
||||
@@ -20,6 +20,7 @@ type ExistedUserInfo = {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
avatar?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
removed?: false;
|
||||
};
|
||||
|
||||
@@ -90,6 +91,7 @@ export class PublicUserService extends Service {
|
||||
id,
|
||||
name: user.name,
|
||||
avatar: user.avatarUrl,
|
||||
avatarUrl: user.avatarUrl,
|
||||
};
|
||||
}).pipe(
|
||||
smartRetry(),
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { WorkspacePropertyFilter } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class CheckboxPropertyFilterProvider
|
||||
extends Service
|
||||
implements FilterProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const method = params.method as WorkspacePropertyFilter<'checkbox'>;
|
||||
if (method === 'is') {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if ((value === 'true' ? 'true' : 'false') === params.value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
}
|
||||
if (method === 'is-not') {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if ((value === 'true' ? 'true' : 'false') !== params.value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error(`Unsupported method: ${params.method}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
import { basicDateFilter } from './date';
|
||||
|
||||
export class CreatedAtFilterProvider extends Service implements FilterProvider {
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
return this.docsService.allDocsCreatedDate$().pipe(
|
||||
map(docs => new Map(docs.map(doc => [doc.id, doc.createDate]))),
|
||||
basicDateFilter(params)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { WorkspacePropertyFilter } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class CreatedByFilterProvider extends Service implements FilterProvider {
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const method = params.method as WorkspacePropertyFilter<'createdBy'>;
|
||||
if (method === 'include') {
|
||||
const userIds = params.value?.split(',') ?? [];
|
||||
|
||||
return this.docsService.propertyValues$('createdBy').pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if (value && userIds.includes(value)) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error(`Unsupported method: ${params.method}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { WorkspacePropertyFilter } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import dayjs, { type Dayjs, isDayjs } from 'dayjs';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class DatePropertyFilterProvider
|
||||
extends Service
|
||||
implements FilterProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
return this.docsService
|
||||
.propertyValues$('custom:' + params.key)
|
||||
.pipe(basicDateFilter(params));
|
||||
}
|
||||
}
|
||||
|
||||
export function basicDateFilter(
|
||||
params: FilterParams
|
||||
): (
|
||||
upstream$: Observable<Map<string, string | number | undefined>>
|
||||
) => Observable<Set<string>> {
|
||||
return upstream$ => {
|
||||
// value can be like "2025-01-01,2025-01-02"
|
||||
// or "2025-01-01"
|
||||
const filterValues = (params.value
|
||||
?.split(',')
|
||||
.map(t => parseDate(t))
|
||||
.filter(Boolean) ?? []) as [number, number, number][];
|
||||
|
||||
const now = dayjs();
|
||||
const method = params.method as WorkspacePropertyFilter<'date'>;
|
||||
|
||||
const relativeRanges: Record<string, Dayjs> = {
|
||||
'last-3-days': now.subtract(3, 'day'),
|
||||
'last-7-days': now.subtract(7, 'day'),
|
||||
'last-15-days': now.subtract(15, 'day'),
|
||||
'last-30-days': now.subtract(30, 'day'),
|
||||
'this-week': now.startOf('week'),
|
||||
'this-month': now.startOf('month'),
|
||||
// @ts-expect-error 'quarter' is not in type, but it's supported by dayjs
|
||||
'this-quarter': now.startOf('quarter'),
|
||||
'this-year': now.startOf('year'),
|
||||
};
|
||||
|
||||
return upstream$.pipe(
|
||||
map(o => {
|
||||
if (method === 'is-empty' || method === 'is-not-empty') {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if (method === 'is-empty' ? !value : !!value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
if (method === 'between' && filterValues.length >= 2) {
|
||||
return handleDateRangeFilter(
|
||||
o,
|
||||
parsed =>
|
||||
isAfter(parsed, filterValues[0]) &&
|
||||
isBefore(parsed, filterValues[1])
|
||||
);
|
||||
}
|
||||
|
||||
if (method === 'after' && filterValues.length >= 1) {
|
||||
return handleDateRangeFilter(o, parsed =>
|
||||
isAfter(parsed, filterValues[0])
|
||||
);
|
||||
}
|
||||
|
||||
if (method === 'before' && filterValues.length >= 1) {
|
||||
return handleDateRangeFilter(o, parsed =>
|
||||
isBefore(parsed, filterValues[0])
|
||||
);
|
||||
}
|
||||
|
||||
if (method in relativeRanges) {
|
||||
return handleDateRangeFilter(o, parsed =>
|
||||
isAfter(parsed, relativeRanges[method])
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported method: ${method}`);
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function parseDate(value: string | number): [number, number, number] | null {
|
||||
if (typeof value === 'string') {
|
||||
const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const [_, year, month, day] = match;
|
||||
return [parseInt(year), parseInt(month), parseInt(day)];
|
||||
} else if (typeof value === 'number') {
|
||||
const date = new Date(value);
|
||||
return [date.getFullYear(), date.getMonth() + 1, date.getDate()];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleDateRangeFilter(
|
||||
propertyValues: Map<string, string | number | undefined>,
|
||||
predicate: (parsed: [number, number, number]) => boolean
|
||||
): Set<string> {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of propertyValues) {
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
const parsed = parseDate(value);
|
||||
if (parsed && predicate(parsed)) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
function isAfter(
|
||||
targetDate: readonly [number, number, number] | Dayjs,
|
||||
referenceDate: readonly [number, number, number] | Dayjs
|
||||
): boolean {
|
||||
const [targetYear, targetMonth, targetDay] = isDayjs(targetDate)
|
||||
? [targetDate.year(), targetDate.month(), targetDate.date()]
|
||||
: targetDate;
|
||||
const [refYear, refMonth, refDay] = isDayjs(referenceDate)
|
||||
? [referenceDate.year(), referenceDate.month(), referenceDate.date()]
|
||||
: referenceDate;
|
||||
|
||||
return (
|
||||
targetYear >= refYear ||
|
||||
(targetYear === refYear && targetMonth >= refMonth) ||
|
||||
(targetYear === refYear && targetMonth === refMonth && targetDay >= refDay)
|
||||
);
|
||||
}
|
||||
|
||||
function isBefore(
|
||||
targetDate: [number, number, number],
|
||||
referenceDate: [number, number, number]
|
||||
): boolean {
|
||||
const [targetYear, targetMonth, targetDay] = targetDate;
|
||||
const [refYear, refMonth, refDay] = referenceDate;
|
||||
|
||||
return (
|
||||
targetYear <= refYear ||
|
||||
(targetYear === refYear && targetMonth <= refMonth) ||
|
||||
(targetYear === refYear && targetMonth === refMonth && targetDay <= refDay)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { WorkspacePropertyFilter } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class DocPrimaryModeFilterProvider
|
||||
extends Service
|
||||
implements FilterProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const method = params.method as WorkspacePropertyFilter<'docPrimaryMode'>;
|
||||
if (method === 'is') {
|
||||
return this.docsService.propertyValues$('primaryMode').pipe(
|
||||
map(values => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of values) {
|
||||
if ((value ?? 'page') === params.value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
} else if (method === 'is-not') {
|
||||
return this.docsService.propertyValues$('primaryMode').pipe(
|
||||
map(values => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of values) {
|
||||
if ((value ?? 'page') !== params.value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error(`Unsupported method: ${params.method}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { WorkspacePropertyFilter } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class JournalFilterProvider extends Service implements FilterProvider {
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const method = params.method as WorkspacePropertyFilter<'journal'>;
|
||||
if (method === 'is') {
|
||||
return this.docsService.propertyValues$('journal').pipe(
|
||||
map(values => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of values) {
|
||||
if (!!value === (params.value === 'true' ? true : false)) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
} else if (method === 'is-not') {
|
||||
return this.docsService.propertyValues$('journal').pipe(
|
||||
map(values => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of values) {
|
||||
if (!value === (params.value === 'true' ? true : false)) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error(`Unsupported method: ${params.method}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { type Observable, switchMap } from 'rxjs';
|
||||
|
||||
import { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class PropertyFilterProvider extends Service implements FilterProvider {
|
||||
constructor(
|
||||
private readonly workspacePropertyService: WorkspacePropertyService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const property$ = this.workspacePropertyService.propertyInfo$(params.key);
|
||||
|
||||
return property$.pipe(
|
||||
switchMap(property => {
|
||||
if (!property) {
|
||||
throw new Error('Unknown property');
|
||||
}
|
||||
const type = property.type;
|
||||
const provider = this.framework.getOptional(
|
||||
FilterProvider('property:' + type)
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error('Unsupported property type');
|
||||
}
|
||||
return provider.filter$(params);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { type Observable } from 'rxjs';
|
||||
|
||||
import { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class SystemFilterProvider extends Service implements FilterProvider {
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const provider = this.framework.getOptional(
|
||||
FilterProvider('system:' + params.key)
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error('Unsupported system filter: ' + params.key);
|
||||
}
|
||||
return provider.filter$(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { TagService } from '@affine/core/modules/tag';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { combineLatest, map, type Observable, of, switchMap } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class TagsFilterProvider extends Service implements FilterProvider {
|
||||
constructor(
|
||||
private readonly tagService: TagService,
|
||||
private readonly docsService: DocsService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
if (params.method === 'include') {
|
||||
const tagIds = params.value?.split(',') ?? [];
|
||||
|
||||
const tags = tagIds.map(id => this.tagService.tagList.tagByTagId$(id));
|
||||
|
||||
if (tags.length === 0) {
|
||||
return of(new Set<string>());
|
||||
}
|
||||
|
||||
return combineLatest(tags).pipe(
|
||||
switchMap(tags =>
|
||||
combineLatest(
|
||||
tags.filter(tag => tag !== undefined).map(tag => tag.pageIds$)
|
||||
).pipe(map(pageIds => new Set(pageIds.flat())))
|
||||
)
|
||||
);
|
||||
} else if (params.method === 'is-not-empty') {
|
||||
return combineLatest([
|
||||
this.tagService.tagList.tags$.map(tags => new Set(tags.map(t => t.id))),
|
||||
this.docsService.allDocsTagIds$(),
|
||||
]).pipe(
|
||||
map(
|
||||
([tags, docs]) =>
|
||||
new Set(
|
||||
docs
|
||||
.filter(
|
||||
// filter deleted tags
|
||||
// oxlint-disable-next-line prefer-array-some
|
||||
doc => doc.tags.filter(tag => tags.has(tag)).length > 0
|
||||
)
|
||||
.map(doc => doc.id)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (params.method === 'is-empty') {
|
||||
return this.tagService.tagList.tags$
|
||||
.map(tags => new Set(tags.map(t => t.id)))
|
||||
.pipe(
|
||||
switchMap(tags =>
|
||||
this.docsService.allDocsTagIds$().pipe(
|
||||
map(docs => {
|
||||
return new Set(
|
||||
docs
|
||||
.filter(
|
||||
// filter deleted tags
|
||||
// oxlint-disable-next-lint prefer-array-some
|
||||
doc => doc.tags.filter(tag => tags.has(tag)).length === 0
|
||||
)
|
||||
.map(doc => doc.id)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
throw new Error(`Unsupported method: ${params.method}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { WorkspacePropertyFilter } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class TextPropertyFilterProvider
|
||||
extends Service
|
||||
implements FilterProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const method = params.method as WorkspacePropertyFilter<'text'>;
|
||||
if (method === 'is') {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if (value === params.value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
} else if (method === 'is-not') {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if (value !== params.value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
} else if (method === 'is-not-empty') {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if (value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
} else if (method === 'is-empty') {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if (!value) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error(`Unsupported method: ${method}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
import { basicDateFilter } from './date';
|
||||
|
||||
export class UpdatedAtFilterProvider extends Service implements FilterProvider {
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
return this.docsService.allDocsUpdatedDate$().pipe(
|
||||
map(docs => new Map(docs.map(doc => [doc.id, doc.updatedDate]))),
|
||||
basicDateFilter(params)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { WorkspacePropertyFilter } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { FilterProvider } from '../../provider';
|
||||
import type { FilterParams } from '../../types';
|
||||
|
||||
export class UpdatedByFilterProvider extends Service implements FilterProvider {
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
filter$(params: FilterParams): Observable<Set<string>> {
|
||||
const method = params.method as WorkspacePropertyFilter<'updatedBy'>;
|
||||
if (method === 'include') {
|
||||
const userIds = params.value?.split(',') ?? [];
|
||||
|
||||
return this.docsService.propertyValues$('updatedBy').pipe(
|
||||
map(o => {
|
||||
const match = new Set<string>();
|
||||
for (const [id, value] of o) {
|
||||
if (value && userIds.includes(value)) {
|
||||
match.add(id);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error(`Unsupported method: ${params.method}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class CheckboxPropertyGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(values => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const [id, value] of values) {
|
||||
// Treat undefined or any non-true value as false for checkbox grouping
|
||||
const v = value === 'true' ? 'true' : 'false';
|
||||
const set = result.get(v) ?? new Set<string>();
|
||||
set.add(id);
|
||||
result.set(v, set);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class CreatedAtGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
_params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.allDocsCreatedDate$().pipe(
|
||||
map(docs => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
docs.forEach(doc => {
|
||||
if (!doc.createDate) {
|
||||
return;
|
||||
}
|
||||
const date = new Date(doc.createDate);
|
||||
const formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
|
||||
if (!result.has(formattedDate)) {
|
||||
result.set(formattedDate, new Set([doc.id]));
|
||||
} else {
|
||||
result.get(formattedDate)?.add(doc.id);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class CreatedByGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
_params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.propertyValues$('createdBy').pipe(
|
||||
map(o => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const [id, value] of o) {
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
const set = result.get(value) ?? new Set<string>();
|
||||
set.add(id);
|
||||
result.set(value, set);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class DatePropertyGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const [id, value] of o) {
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
const set = result.get(value) ?? new Set<string>();
|
||||
set.add(id);
|
||||
result.set(value, set);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class DocPrimaryModeGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
_params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.propertyValues$('primaryMode').pipe(
|
||||
map(values => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const [id, value] of values) {
|
||||
const mode = value ?? 'page';
|
||||
if (!result.has(mode)) {
|
||||
result.set(mode, new Set([id]));
|
||||
} else {
|
||||
result.get(mode)?.add(id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class JournalGroupByProvider extends Service implements GroupByProvider {
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
_params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.propertyValues$('journal').pipe(
|
||||
map(values => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const [id, value] of values) {
|
||||
const isJournal = value ? 'true' : 'false';
|
||||
if (!result.has(isJournal)) {
|
||||
result.set(isJournal, new Set([id]));
|
||||
} else {
|
||||
result.get(isJournal)?.add(id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { switchMap } from 'rxjs';
|
||||
|
||||
import { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class PropertyGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(
|
||||
private readonly workspacePropertyService: WorkspacePropertyService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
groupBy$(
|
||||
items$: Observable<Set<string>>,
|
||||
params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
const property$ = this.workspacePropertyService.propertyInfo$(params.key);
|
||||
|
||||
return property$.pipe(
|
||||
switchMap(property => {
|
||||
if (!property) {
|
||||
throw new Error('Unknown property');
|
||||
}
|
||||
const type = property.type;
|
||||
const provider = this.framework.getOptional(
|
||||
GroupByProvider('property:' + type)
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error('Unsupported property type');
|
||||
}
|
||||
return provider.groupBy$(items$, params);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class SystemGroupByProvider extends Service implements GroupByProvider {
|
||||
groupBy$(
|
||||
items$: Observable<Set<string>>,
|
||||
params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
const provider = this.framework.getOptional(
|
||||
GroupByProvider('system:' + params.key)
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error('Unsupported system group by: ' + params.key);
|
||||
}
|
||||
return provider.groupBy$(items$, params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { TagService } from '@affine/core/modules/tag';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { combineLatest, map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class TagsGroupByProvider extends Service implements GroupByProvider {
|
||||
constructor(
|
||||
private readonly docsService: DocsService,
|
||||
private readonly tagService: TagService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
_params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return combineLatest([
|
||||
this.tagService.tagList.tags$.map(tags => new Set(tags.map(t => t.id))),
|
||||
this.docsService.allDocsTagIds$(),
|
||||
]).pipe(
|
||||
map(([existsTags, docs]) => {
|
||||
const map = new Map<string, Set<string>>();
|
||||
|
||||
for (const { id, tags } of docs) {
|
||||
for (const tag of tags) {
|
||||
if (!existsTags.has(tag)) {
|
||||
continue;
|
||||
}
|
||||
const set = map.get(tag) ?? new Set();
|
||||
set.add(id);
|
||||
map.set(tag, set);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class TextPropertyGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const [id, value] of o) {
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
const set = result.get(value) ?? new Set<string>();
|
||||
set.add(id);
|
||||
result.set(value, set);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class UpdatedAtGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
groupBy$(
|
||||
// not used in this implementation
|
||||
_items$: Observable<Set<string>>,
|
||||
_params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.allDocsUpdatedDate$().pipe(
|
||||
map(docs => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
docs.forEach(doc => {
|
||||
if (!doc.updatedDate) {
|
||||
return;
|
||||
}
|
||||
const date = new Date(doc.updatedDate);
|
||||
const formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
|
||||
if (!result.has(formattedDate)) {
|
||||
result.set(formattedDate, new Set([doc.id]));
|
||||
} else {
|
||||
result.get(formattedDate)?.add(doc.id);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GroupByProvider } from '../../provider';
|
||||
import type { GroupByParams } from '../../types';
|
||||
|
||||
export class UpdatedByGroupByProvider
|
||||
extends Service
|
||||
implements GroupByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
groupBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
_params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>> {
|
||||
return this.docsService.propertyValues$('updatedBy').pipe(
|
||||
map(o => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const [id, value] of o) {
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
const set = result.get(value) ?? new Set<string>();
|
||||
set.add(id);
|
||||
result.set(value, set);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { combineLatest, map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class CheckboxPropertyOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const isDesc = params.desc;
|
||||
return combineLatest([
|
||||
this.docsService.list.docs$, // We need the complete doc list as docs without property values should default to false
|
||||
this.docsService.propertyValues$('custom:' + params.key),
|
||||
]).pipe(
|
||||
map(([docs, values]) => {
|
||||
const result: [string, boolean][] = [];
|
||||
for (const doc of docs) {
|
||||
const value = values.get(doc.id) === 'true' ? true : false;
|
||||
result.push([doc.id, value]);
|
||||
}
|
||||
return result
|
||||
.sort(
|
||||
(a, b) => (a[1] === b[1] ? 0 : a[1] ? 1 : -1) * (isDesc ? -1 : 1)
|
||||
)
|
||||
.map(i => i[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class CreatedAtOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
return this.docsService.allDocsCreatedDate$().pipe(
|
||||
map(docs => {
|
||||
if (params.desc) {
|
||||
return docs
|
||||
.sort((a, b) => (b.createDate ?? 0) - (a.createDate ?? 0))
|
||||
.map(doc => doc.id);
|
||||
} else {
|
||||
return docs
|
||||
.sort((a, b) => (a.createDate ?? 0) - (b.createDate ?? 0))
|
||||
.map(doc => doc.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class CreatedByOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const isDesc = params.desc;
|
||||
return this.docsService.propertyValues$('createdBy').pipe(
|
||||
map(o => {
|
||||
return Array.from(o)
|
||||
.filter((i): i is [string, string] => !!i[1]) // filter empty value
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a[1] === b[1] ? 0 : a[1] > b[1] ? 1 : -1) * (isDesc ? -1 : 1)
|
||||
)
|
||||
.map(i => i[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class DatePropertyOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const isDesc = params.desc;
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
return Array.from(o)
|
||||
.filter((i): i is [string, string] => !!i[1]) // filter empty value
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a[1] === b[1] ? 0 : a[1] > b[1] ? 1 : -1) * (isDesc ? -1 : 1)
|
||||
)
|
||||
.map(i => i[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class DocPrimaryModeOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
return this.docsService.propertyValues$('primaryMode').pipe(
|
||||
map(values => {
|
||||
const docs = Array.from(values).map(([id, value]) => ({
|
||||
id,
|
||||
mode: value ?? 'page',
|
||||
}));
|
||||
|
||||
if (params.desc) {
|
||||
return docs
|
||||
.sort((a, b) => b.mode.localeCompare(a.mode))
|
||||
.map(doc => doc.id);
|
||||
} else {
|
||||
return docs
|
||||
.sort((a, b) => a.mode.localeCompare(b.mode))
|
||||
.map(doc => doc.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class JournalOrderByProvider extends Service implements OrderByProvider {
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const isDesc = params.desc;
|
||||
return this.docsService.propertyValues$('journal').pipe(
|
||||
map(values => {
|
||||
return Array.from(values)
|
||||
.map(([id, value]) => ({
|
||||
id,
|
||||
isJournal: !!value,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
if (a.isJournal === b.isJournal) {
|
||||
return 0;
|
||||
}
|
||||
return (a.isJournal ? 1 : -1) * (isDesc ? -1 : 1);
|
||||
})
|
||||
.map(doc => doc.id);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { type Observable, switchMap } from 'rxjs';
|
||||
|
||||
import { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class PropertyOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(
|
||||
private readonly workspacePropertyService: WorkspacePropertyService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
orderBy$(
|
||||
items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const property$ = this.workspacePropertyService.propertyInfo$(params.key);
|
||||
|
||||
return property$.pipe(
|
||||
switchMap(property => {
|
||||
if (!property) {
|
||||
throw new Error('Unknown property');
|
||||
}
|
||||
const type = property.type;
|
||||
const provider = this.framework.getOptional(
|
||||
OrderByProvider('property:' + type)
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error('Unsupported property type');
|
||||
}
|
||||
return provider.orderBy$(items$, params);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class SystemOrderByProvider extends Service implements OrderByProvider {
|
||||
orderBy$(
|
||||
items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const provider = this.framework.getOptional(
|
||||
OrderByProvider('system:' + params.key)
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error('Unsupported system order by: ' + params.key);
|
||||
}
|
||||
return provider.orderBy$(items$, params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import type { TagService } from '@affine/core/modules/tag';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { combineLatest, map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class TagsOrderByProvider extends Service implements OrderByProvider {
|
||||
constructor(
|
||||
private readonly docsService: DocsService,
|
||||
private readonly tagService: TagService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const isDesc = params.desc;
|
||||
return combineLatest([
|
||||
this.tagService.tagList.tags$.map(tags => new Set(tags.map(t => t.id))),
|
||||
this.docsService.allDocsTagIds$(),
|
||||
]).pipe(
|
||||
map(([existsTags, docs]) =>
|
||||
docs
|
||||
.map(doc => {
|
||||
const filteredTags = doc.tags
|
||||
.filter(tag => existsTags.has(tag)) // filter out tags that don't exist
|
||||
.sort() // sort tags by ids
|
||||
.join(','); // convert to string
|
||||
return [doc.id, filteredTags];
|
||||
})
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a[1] === b[1] ? 0 : a[1] > b[1] ? 1 : -1) * (isDesc ? -1 : 1)
|
||||
)
|
||||
.map(i => i[0])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class TextPropertyOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const isDesc = params.desc;
|
||||
return this.docsService.propertyValues$('custom:' + params.key).pipe(
|
||||
map(o => {
|
||||
return Array.from(o)
|
||||
.filter((i): i is [string, string] => !!i[1]) // filter empty value
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a[1] === b[1] ? 0 : a[1] > b[1] ? 1 : -1) * (isDesc ? -1 : 1)
|
||||
)
|
||||
.map(i => i[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class UpdatedAtOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
return this.docsService.allDocsUpdatedDate$().pipe(
|
||||
map(docs => {
|
||||
if (params.desc) {
|
||||
return docs
|
||||
.filter(doc => doc.updatedDate)
|
||||
.sort((a, b) => (b.updatedDate ?? 0) - (a.updatedDate ?? 0))
|
||||
.map(doc => doc.id);
|
||||
} else {
|
||||
return docs
|
||||
.filter(doc => doc.updatedDate)
|
||||
.sort((a, b) => (a.updatedDate ?? 0) - (b.updatedDate ?? 0))
|
||||
.map(doc => doc.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { DocsService } from '@affine/core/modules/doc';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { OrderByProvider } from '../../provider';
|
||||
import type { OrderByParams } from '../../types';
|
||||
|
||||
export class UpdatedByOrderByProvider
|
||||
extends Service
|
||||
implements OrderByProvider
|
||||
{
|
||||
constructor(private readonly docsService: DocsService) {
|
||||
super();
|
||||
}
|
||||
orderBy$(
|
||||
_items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]> {
|
||||
const isDesc = params.desc;
|
||||
return this.docsService.propertyValues$('updatedBy').pipe(
|
||||
map(o => {
|
||||
return Array.from(o)
|
||||
.filter((i): i is [string, string] => !!i[1]) // filter empty value
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a[1] === b[1] ? 0 : a[1] > b[1] ? 1 : -1) * (isDesc ? -1 : 1)
|
||||
)
|
||||
.map(i => i[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocsService } from '../doc';
|
||||
import { TagService } from '../tag';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { WorkspacePropertyService } from '../workspace-property';
|
||||
import { CheckboxPropertyFilterProvider } from './impls/filters/checkbox';
|
||||
import { CreatedAtFilterProvider } from './impls/filters/created-at';
|
||||
import { CreatedByFilterProvider } from './impls/filters/created-by';
|
||||
import { DatePropertyFilterProvider } from './impls/filters/date';
|
||||
import { DocPrimaryModeFilterProvider } from './impls/filters/doc-primary-mode';
|
||||
import { JournalFilterProvider } from './impls/filters/journal';
|
||||
import { PropertyFilterProvider } from './impls/filters/property';
|
||||
import { SystemFilterProvider } from './impls/filters/system';
|
||||
import { TagsFilterProvider } from './impls/filters/tags';
|
||||
import { TextPropertyFilterProvider } from './impls/filters/text';
|
||||
import { UpdatedAtFilterProvider } from './impls/filters/updated-at';
|
||||
import { UpdatedByFilterProvider } from './impls/filters/updated-by';
|
||||
import { CheckboxPropertyGroupByProvider } from './impls/group-by/checkbox';
|
||||
import { CreatedAtGroupByProvider } from './impls/group-by/created-at';
|
||||
import { CreatedByGroupByProvider } from './impls/group-by/created-by';
|
||||
import { DatePropertyGroupByProvider } from './impls/group-by/date';
|
||||
import { DocPrimaryModeGroupByProvider } from './impls/group-by/doc-primary-mode';
|
||||
import { JournalGroupByProvider } from './impls/group-by/journal';
|
||||
import { PropertyGroupByProvider } from './impls/group-by/property';
|
||||
import { SystemGroupByProvider } from './impls/group-by/system';
|
||||
import { TagsGroupByProvider } from './impls/group-by/tags';
|
||||
import { TextPropertyGroupByProvider } from './impls/group-by/text';
|
||||
import { UpdatedAtGroupByProvider } from './impls/group-by/updated-at';
|
||||
import { UpdatedByGroupByProvider } from './impls/group-by/updated-by';
|
||||
import { CheckboxPropertyOrderByProvider } from './impls/order-by/checkbox';
|
||||
import { CreatedAtOrderByProvider } from './impls/order-by/created-at';
|
||||
import { CreatedByOrderByProvider } from './impls/order-by/created-by';
|
||||
import { DatePropertyOrderByProvider } from './impls/order-by/date';
|
||||
import { DocPrimaryModeOrderByProvider } from './impls/order-by/doc-primary-mode';
|
||||
import { JournalOrderByProvider } from './impls/order-by/journal';
|
||||
import { PropertyOrderByProvider } from './impls/order-by/property';
|
||||
import { SystemOrderByProvider } from './impls/order-by/system';
|
||||
import { TagsOrderByProvider } from './impls/order-by/tags';
|
||||
import { TextPropertyOrderByProvider } from './impls/order-by/text';
|
||||
import { UpdatedAtOrderByProvider } from './impls/order-by/updated-at';
|
||||
import { UpdatedByOrderByProvider } from './impls/order-by/updated-by';
|
||||
import { FilterProvider, GroupByProvider, OrderByProvider } from './provider';
|
||||
import { CollectionRulesService } from './services/collection-rules';
|
||||
|
||||
export { CollectionRulesService } from './services/collection-rules';
|
||||
export type { FilterParams } from './types';
|
||||
|
||||
export function configureCollectionRulesModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(CollectionRulesService)
|
||||
// --------------- Filter ---------------
|
||||
.impl(FilterProvider('system'), SystemFilterProvider)
|
||||
.impl(FilterProvider('property'), PropertyFilterProvider, [
|
||||
WorkspacePropertyService,
|
||||
])
|
||||
.impl(FilterProvider('property:checkbox'), CheckboxPropertyFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('property:text'), TextPropertyFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('property:tags'), TagsFilterProvider, [
|
||||
TagService,
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('system:tags'), TagsFilterProvider, [
|
||||
TagService,
|
||||
DocsService,
|
||||
])
|
||||
.impl(
|
||||
FilterProvider('property:docPrimaryMode'),
|
||||
DocPrimaryModeFilterProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(
|
||||
FilterProvider('system:docPrimaryMode'),
|
||||
DocPrimaryModeFilterProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(FilterProvider('property:date'), DatePropertyFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('property:createdAt'), CreatedAtFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('system:createdAt'), CreatedAtFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('property:updatedAt'), UpdatedAtFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('system:updatedAt'), UpdatedAtFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('property:journal'), JournalFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('system:journal'), JournalFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('property:createdBy'), CreatedByFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('system:createdBy'), CreatedByFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('property:updatedBy'), UpdatedByFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(FilterProvider('system:updatedBy'), UpdatedByFilterProvider, [
|
||||
DocsService,
|
||||
])
|
||||
// --------------- Group By ---------------
|
||||
.impl(GroupByProvider('system'), SystemGroupByProvider)
|
||||
.impl(GroupByProvider('property'), PropertyGroupByProvider, [
|
||||
WorkspacePropertyService,
|
||||
])
|
||||
.impl(GroupByProvider('property:date'), DatePropertyGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('property:tags'), TagsGroupByProvider, [
|
||||
DocsService,
|
||||
TagService,
|
||||
])
|
||||
.impl(GroupByProvider('system:tags'), TagsGroupByProvider, [
|
||||
DocsService,
|
||||
TagService,
|
||||
])
|
||||
.impl(
|
||||
GroupByProvider('property:checkbox'),
|
||||
CheckboxPropertyGroupByProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(GroupByProvider('property:text'), TextPropertyGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(
|
||||
GroupByProvider('property:docPrimaryMode'),
|
||||
DocPrimaryModeGroupByProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(
|
||||
GroupByProvider('system:docPrimaryMode'),
|
||||
DocPrimaryModeGroupByProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(GroupByProvider('property:createdAt'), CreatedAtGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('system:createdAt'), CreatedAtGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('property:updatedAt'), UpdatedAtGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('system:updatedAt'), UpdatedAtGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('property:journal'), JournalGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('system:journal'), JournalGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('property:createdBy'), CreatedByGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('system:createdBy'), CreatedByGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('property:updatedBy'), UpdatedByGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(GroupByProvider('system:updatedBy'), UpdatedByGroupByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
// --------------- Order By ---------------
|
||||
.impl(OrderByProvider('system'), SystemOrderByProvider)
|
||||
.impl(OrderByProvider('property'), PropertyOrderByProvider, [
|
||||
WorkspacePropertyService,
|
||||
])
|
||||
.impl(
|
||||
OrderByProvider('property:docPrimaryMode'),
|
||||
DocPrimaryModeOrderByProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(
|
||||
OrderByProvider('system:docPrimaryMode'),
|
||||
DocPrimaryModeOrderByProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(OrderByProvider('property:updatedAt'), UpdatedAtOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('system:updatedAt'), UpdatedAtOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('property:createdAt'), CreatedAtOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('system:createdAt'), CreatedAtOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('property:text'), TextPropertyOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('property:date'), DatePropertyOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(
|
||||
OrderByProvider('property:checkbox'),
|
||||
CheckboxPropertyOrderByProvider,
|
||||
[DocsService]
|
||||
)
|
||||
.impl(OrderByProvider('property:tags'), TagsOrderByProvider, [
|
||||
DocsService,
|
||||
TagService,
|
||||
])
|
||||
.impl(OrderByProvider('system:tags'), TagsOrderByProvider, [
|
||||
DocsService,
|
||||
TagService,
|
||||
])
|
||||
.impl(OrderByProvider('property:journal'), JournalOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('system:journal'), JournalOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('property:createdBy'), CreatedByOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('system:createdBy'), CreatedByOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('property:updatedBy'), UpdatedByOrderByProvider, [
|
||||
DocsService,
|
||||
])
|
||||
.impl(OrderByProvider('system:updatedBy'), UpdatedByOrderByProvider, [
|
||||
DocsService,
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { createIdentifier } from '@toeverything/infra';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import type { FilterParams, GroupByParams, OrderByParams } from '../types';
|
||||
|
||||
export interface FilterProvider {
|
||||
filter$(params: FilterParams): Observable<Set<string>>;
|
||||
}
|
||||
|
||||
export const FilterProvider =
|
||||
createIdentifier<FilterProvider>('FilterProvider');
|
||||
|
||||
export interface GroupByProvider {
|
||||
groupBy$(
|
||||
items$: Observable<Set<string>>,
|
||||
params: GroupByParams
|
||||
): Observable<Map<string, Set<string>>>;
|
||||
}
|
||||
|
||||
export const GroupByProvider =
|
||||
createIdentifier<GroupByProvider>('GroupByProvider');
|
||||
|
||||
export interface OrderByProvider {
|
||||
orderBy$(
|
||||
items$: Observable<Set<string>>,
|
||||
params: OrderByParams
|
||||
): Observable<string[]>;
|
||||
}
|
||||
|
||||
export const OrderByProvider =
|
||||
createIdentifier<OrderByProvider>('OrderByProvider');
|
||||
@@ -0,0 +1,207 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
import {
|
||||
catchError,
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
type Observable,
|
||||
of,
|
||||
share,
|
||||
throttleTime,
|
||||
} from 'rxjs';
|
||||
|
||||
import { FilterProvider, GroupByProvider, OrderByProvider } from '../provider';
|
||||
import type { FilterParams, GroupByParams, OrderByParams } from '../types';
|
||||
|
||||
export class CollectionRulesService extends Service {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
watch(
|
||||
filters: FilterParams[],
|
||||
groupBy?: GroupByParams,
|
||||
orderBy?: OrderByParams
|
||||
): Observable<{
|
||||
groups: {
|
||||
key: string;
|
||||
items: string[];
|
||||
}[];
|
||||
filterErrors: any[];
|
||||
}> {
|
||||
// STEP 1: FILTER
|
||||
const filterProviders = this.framework.getAll(FilterProvider);
|
||||
const filtered$: Observable<{
|
||||
filtered: Set<string>;
|
||||
filterErrors: any[]; // errors from the filter providers
|
||||
}> =
|
||||
filters.length === 0
|
||||
? of({ filtered: new Set<string>(), filterErrors: [] })
|
||||
: combineLatest(
|
||||
filters.map(filter => {
|
||||
const provider = filterProviders.get(filter.type);
|
||||
if (!provider) {
|
||||
return of({
|
||||
error: new Error(`Unsupported filter type: ${filter.type}`),
|
||||
});
|
||||
}
|
||||
return provider.filter$(filter).pipe(
|
||||
distinctUntilChanged((prev, curr) => {
|
||||
return prev.isSubsetOf(curr) && curr.isSubsetOf(prev);
|
||||
}),
|
||||
catchError(error => {
|
||||
console.log(error);
|
||||
return of({ error });
|
||||
})
|
||||
);
|
||||
})
|
||||
).pipe(
|
||||
map(results => {
|
||||
const finalSet = results.reduce((acc, result) => {
|
||||
if ('error' in acc) {
|
||||
return acc;
|
||||
}
|
||||
if ('error' in result) {
|
||||
return acc;
|
||||
}
|
||||
return acc.intersection(result);
|
||||
});
|
||||
|
||||
return {
|
||||
filtered: 'error' in finalSet ? new Set<string>() : finalSet,
|
||||
filterErrors: results.map(i => ('error' in i ? i.error : null)),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// STEP 2: ORDER BY
|
||||
const orderByProvider = orderBy
|
||||
? this.framework.getOptional(OrderByProvider(orderBy.type))
|
||||
: null;
|
||||
const ordered$: Observable<{
|
||||
ordered: string[];
|
||||
filtered: Set<string>;
|
||||
filterErrors: any[];
|
||||
}> = filtered$.pipe(last$ => {
|
||||
if (orderBy && orderByProvider) {
|
||||
const shared$ = last$.pipe(share());
|
||||
const items$ = shared$.pipe(
|
||||
map(i => i.filtered),
|
||||
// avoid re-ordering the same items
|
||||
distinctUntilChanged((prev, curr) => {
|
||||
return prev.isSubsetOf(curr) && curr.isSubsetOf(prev);
|
||||
})
|
||||
);
|
||||
return combineLatest([
|
||||
orderByProvider.orderBy$(items$, orderBy),
|
||||
shared$,
|
||||
]).pipe(
|
||||
map(([ordered, last]) => {
|
||||
return {
|
||||
ordered: Array.from(ordered),
|
||||
...last,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
return last$.pipe(
|
||||
map(last => ({
|
||||
ordered: Array.from(last.filtered),
|
||||
...last,
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// STEP 3: GROUP BY
|
||||
const groupByProvider = groupBy
|
||||
? this.framework.getOptional(GroupByProvider(groupBy.type))
|
||||
: null;
|
||||
const grouped$: Observable<{
|
||||
grouped: Map<string, Set<string>>;
|
||||
ordered: string[];
|
||||
filtered: Set<string>;
|
||||
filterErrors: any[];
|
||||
}> = ordered$.pipe(last$ => {
|
||||
if (groupBy && groupByProvider) {
|
||||
const shared$ = last$.pipe(share());
|
||||
const items$ = shared$.pipe(
|
||||
map(i => i.filtered),
|
||||
// avoid re-grouping the same items
|
||||
distinctUntilChanged((prev, curr) => {
|
||||
return prev.isSubsetOf(curr) && curr.isSubsetOf(prev);
|
||||
})
|
||||
);
|
||||
return combineLatest([
|
||||
groupByProvider.groupBy$(items$, groupBy),
|
||||
shared$,
|
||||
]).pipe(
|
||||
map(([grouped, last]) => {
|
||||
return {
|
||||
grouped: grouped,
|
||||
...last,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
return last$.pipe(
|
||||
map(last => ({
|
||||
grouped: new Map<string, Set<string>>([['', last.filtered]]),
|
||||
...last,
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// STEP 4: Merge the results
|
||||
const final$: Observable<{
|
||||
groups: {
|
||||
key: string;
|
||||
items: string[];
|
||||
}[];
|
||||
filterErrors: any[];
|
||||
}> = grouped$.pipe(
|
||||
throttleTime(500), // throttle the results to avoid too many re-renders
|
||||
map(({ grouped, ordered, filtered, filterErrors }) => {
|
||||
const result: { key: string; items: string[] }[] = [];
|
||||
|
||||
function addToResult(key: string, item: string) {
|
||||
const existing = result.find(i => i.key === key);
|
||||
if (existing) {
|
||||
existing.items.push(item);
|
||||
} else {
|
||||
result.push({ key: key, items: [item] });
|
||||
}
|
||||
}
|
||||
|
||||
// this step ensures that all filtered items are present in ordered
|
||||
const finalOrdered = new Set(ordered.concat(Array.from(filtered)));
|
||||
|
||||
for (const item of finalOrdered) {
|
||||
const included = filtered.has(item);
|
||||
if (!included) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const groups: string[] = [];
|
||||
for (const [group, items] of grouped) {
|
||||
if (items.has(item)) {
|
||||
groups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
if (groups.length === 0) {
|
||||
// ungrouped items
|
||||
addToResult('', item);
|
||||
} else {
|
||||
for (const group of groups) {
|
||||
addToResult(group, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { groups: result, filterErrors };
|
||||
})
|
||||
);
|
||||
|
||||
return final$;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface FilterParams {
|
||||
type: string;
|
||||
key: string;
|
||||
method: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface GroupByParams {
|
||||
type: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface OrderByParams {
|
||||
type: string;
|
||||
key: string;
|
||||
desc?: boolean;
|
||||
}
|
||||
@@ -41,6 +41,9 @@ export class WorkspaceDBTable<
|
||||
find = this.table.find.bind(this.table) as typeof this.table.find;
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
find$ = this.table.find$.bind(this.table) as typeof this.table.find$;
|
||||
select = this.table.select.bind(this.table) as typeof this.table.select;
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
select$ = this.table.select$.bind(this.table) as typeof this.table.select$;
|
||||
keys = this.table.keys.bind(this.table) as typeof this.table.keys;
|
||||
delete = this.table.delete.bind(this.table) as typeof this.table.delete;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import {
|
||||
type DBSchemaBuilder,
|
||||
f,
|
||||
type FieldSchemaBuilder,
|
||||
type ORMEntity,
|
||||
t,
|
||||
} from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import type { WorkspacePropertyType } from '../../workspace-property';
|
||||
|
||||
const integrationType = f.enum('readwise', 'zotero');
|
||||
|
||||
export const AFFiNE_WORKSPACE_DB_SCHEMA = {
|
||||
@@ -31,7 +34,7 @@ export const AFFiNE_WORKSPACE_DB_SCHEMA = {
|
||||
docCustomPropertyInfo: {
|
||||
id: f.string().primaryKey().optional().default(nanoid),
|
||||
name: f.string().optional(),
|
||||
type: f.string(),
|
||||
type: f.string() as FieldSchemaBuilder<WorkspacePropertyType, false, false>,
|
||||
show: f.enum('always-show', 'always-hide', 'hide-when-empty').optional(),
|
||||
index: f.string().optional(),
|
||||
icon: f.string().optional(),
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { CheckboxValue } from '@affine/core/components/doc-properties/types/checkbox';
|
||||
import { CheckboxValue } from '@affine/core/components/workspace-property-types/checkbox';
|
||||
import type { LiveData } from '@toeverything/infra';
|
||||
import { useLiveData } from '@toeverything/infra';
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { DateValue } from '@affine/core/components/doc-properties/types/date';
|
||||
import { DateValue } from '@affine/core/components/workspace-property-types/date';
|
||||
import type { LiveData } from '@toeverything/infra';
|
||||
import { useLiveData } from '@toeverything/infra';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { NumberValue } from '@affine/core/components/doc-properties/types/number';
|
||||
import { NumberValue } from '@affine/core/components/workspace-property-types/number';
|
||||
import { useLiveData } from '@toeverything/infra';
|
||||
|
||||
import type { DatabaseCellRendererProps } from '../../../types';
|
||||
|
||||
+6
-5
@@ -115,7 +115,7 @@ const adapter = {
|
||||
...options,
|
||||
{
|
||||
id: newTag.id,
|
||||
value: newTag.value,
|
||||
value: newTag.name,
|
||||
color: newTag.color,
|
||||
},
|
||||
]);
|
||||
@@ -156,9 +156,10 @@ const BlocksuiteDatabaseSelector = ({
|
||||
let tagOptions = useLiveData(adapter.getTagOptions$(selectCell));
|
||||
|
||||
// adapt bs database old tag color to new tag color
|
||||
tagOptions = useMemo(() => {
|
||||
let adaptedTagOptions = useMemo(() => {
|
||||
return tagOptions.map(tag => ({
|
||||
...tag,
|
||||
id: tag.id,
|
||||
name: tag.value,
|
||||
color: databaseTagColorToV2(tag.color),
|
||||
}));
|
||||
}, [tagOptions]);
|
||||
@@ -168,7 +169,7 @@ const BlocksuiteDatabaseSelector = ({
|
||||
// bs database uses --affine-tag-xxx colors
|
||||
const newTag = {
|
||||
id: nanoid(),
|
||||
value: name,
|
||||
name: name,
|
||||
color: color,
|
||||
};
|
||||
adapter.createTag(selectCell, dataSource, newTag);
|
||||
@@ -228,7 +229,7 @@ const BlocksuiteDatabaseSelector = ({
|
||||
<TagsInlineEditor
|
||||
tagMode="db-label"
|
||||
className={styles.tagInlineEditor}
|
||||
tags={tagOptions}
|
||||
tags={adaptedTagOptions}
|
||||
selectedTags={selectedIds}
|
||||
onCreateTag={onCreateTag}
|
||||
onDeleteTag={onDeleteTag}
|
||||
|
||||
@@ -11,7 +11,6 @@ import type { Framework } from '@toeverything/infra';
|
||||
import { WorkspaceDBService } from '../db/services/db';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { Doc } from './entities/doc';
|
||||
import { DocPropertyList } from './entities/property-list';
|
||||
import { DocRecord } from './entities/record';
|
||||
import { DocRecordList } from './entities/record-list';
|
||||
import { DocCreateMiddleware } from './providers/doc-create-middleware';
|
||||
@@ -35,7 +34,6 @@ export function configureDocModule(framework: Framework) {
|
||||
.store(DocsStore, [WorkspaceService, DocPropertiesStore])
|
||||
.entity(DocRecord, [DocsStore, DocPropertiesStore])
|
||||
.entity(DocRecordList, [DocsStore])
|
||||
.entity(DocPropertyList, [DocPropertiesStore])
|
||||
.scope(DocScope)
|
||||
.entity(Doc, [DocScope, DocsStore, WorkspaceService])
|
||||
.service(DocService);
|
||||
|
||||
@@ -4,15 +4,12 @@ import { replaceIdMiddleware } from '@blocksuite/affine/shared/adapters';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine/shared/types';
|
||||
import type { DeltaInsert } from '@blocksuite/affine/store';
|
||||
import { Slice, Text, Transformer } from '@blocksuite/affine/store';
|
||||
import { LiveData, ObjectPool, Service } from '@toeverything/infra';
|
||||
import { omitBy } from 'lodash-es';
|
||||
import { ObjectPool, Service } from '@toeverything/infra';
|
||||
import { combineLatest, map } from 'rxjs';
|
||||
|
||||
import { initDocFromProps } from '../../../blocksuite/initialization';
|
||||
import type { DocProperties } from '../../db';
|
||||
import { getAFFiNEWorkspaceSchema } from '../../workspace';
|
||||
import type { Doc } from '../entities/doc';
|
||||
import { DocPropertyList } from '../entities/property-list';
|
||||
import { DocRecordList } from '../entities/record-list';
|
||||
import { DocCreated, DocInitialized } from '../events';
|
||||
import type { DocCreateMiddleware } from '../providers/doc-create-middleware';
|
||||
@@ -33,26 +30,44 @@ export class DocsService extends Service {
|
||||
},
|
||||
});
|
||||
|
||||
propertyList = this.framework.createEntity(DocPropertyList);
|
||||
/**
|
||||
* Get all property values of a property, used for search
|
||||
*
|
||||
* Results may include docs in trash or deleted docs
|
||||
* Legacy property data such as old `journal` will not be included in the values
|
||||
*/
|
||||
propertyValues$(propertyKey: string) {
|
||||
return combineLatest([
|
||||
this.store.watchDocIds(),
|
||||
this.docPropertiesStore.watchPropertyAllValues(propertyKey),
|
||||
]).pipe(
|
||||
map(([docIds, propertyValues]) => {
|
||||
const result = new Map<string, string | undefined>();
|
||||
for (const docId of docIds) {
|
||||
result.set(docId, propertyValues.get(docId));
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* used for search doc by properties, for convenience of search, all non-exist doc or trash doc have been filtered
|
||||
* used for search
|
||||
*/
|
||||
allDocProperties$: LiveData<Record<string, DocProperties>> = LiveData.from(
|
||||
combineLatest([
|
||||
this.docPropertiesStore.watchAllDocProperties(),
|
||||
this.store.watchNonTrashDocIds(),
|
||||
]).pipe(
|
||||
map(([properties, docIds]) => {
|
||||
const allIds = new Set(docIds);
|
||||
return omitBy(
|
||||
properties as Record<string, DocProperties>,
|
||||
(_, id) => !allIds.has(id)
|
||||
);
|
||||
})
|
||||
),
|
||||
{}
|
||||
);
|
||||
allDocsCreatedDate$() {
|
||||
return this.store.watchAllDocCreateDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* used for search
|
||||
*/
|
||||
allDocsUpdatedDate$() {
|
||||
return this.store.watchAllDocUpdatedDate();
|
||||
}
|
||||
|
||||
allDocsTagIds$() {
|
||||
return this.store.watchAllDocTagIds();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly store: DocsStore,
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import { Store, yjsObserveByPath, yjsObserveDeep } from '@toeverything/infra';
|
||||
import { differenceBy, isNil, omitBy } from 'lodash-es';
|
||||
import {
|
||||
LiveData,
|
||||
Store,
|
||||
yjsGetPath,
|
||||
yjsObserveDeep,
|
||||
} from '@toeverything/infra';
|
||||
import { isNil, omitBy } from 'lodash-es';
|
||||
import { combineLatest, map, switchMap } from 'rxjs';
|
||||
import { AbstractType as YAbstractType } from 'yjs';
|
||||
|
||||
import type { WorkspaceDBService } from '../../db';
|
||||
import type {
|
||||
DocCustomPropertyInfo,
|
||||
DocProperties,
|
||||
} from '../../db/schema/schema';
|
||||
import type { DocProperties } from '../../db/schema/schema';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { BUILT_IN_CUSTOM_PROPERTY_TYPE } from '../constants';
|
||||
|
||||
interface LegacyDocProperties {
|
||||
custom?: Record<string, { value: unknown } | undefined>;
|
||||
system?: Record<string, { value: unknown } | undefined>;
|
||||
}
|
||||
|
||||
type LegacyDocPropertyInfo = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
type LegacyDocPropertyInfoList = Record<
|
||||
string,
|
||||
LegacyDocPropertyInfo | undefined
|
||||
>;
|
||||
|
||||
export class DocPropertiesStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
@@ -43,92 +32,6 @@ export class DocPropertiesStore extends Store {
|
||||
});
|
||||
}
|
||||
|
||||
getDocPropertyInfoList() {
|
||||
const db = this.dbService.db.docCustomPropertyInfo.find();
|
||||
const legacy = this.upgradeLegacyDocPropertyInfoList(
|
||||
this.getLegacyDocPropertyInfoList()
|
||||
);
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
|
||||
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
|
||||
const all = [
|
||||
...withLegacy,
|
||||
...differenceBy(builtIn, withLegacy, i => i.id),
|
||||
];
|
||||
return all.filter(i => !i.isDeleted);
|
||||
}
|
||||
|
||||
createDocPropertyInfo(
|
||||
config: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
|
||||
) {
|
||||
return this.dbService.db.docCustomPropertyInfo.create(config);
|
||||
}
|
||||
|
||||
removeDocPropertyInfo(id: string) {
|
||||
this.updateDocPropertyInfo(id, {
|
||||
additionalData: {}, // also remove additional data to reduce size
|
||||
isDeleted: true,
|
||||
});
|
||||
}
|
||||
|
||||
updateDocPropertyInfo(id: string, config: Partial<DocCustomPropertyInfo>) {
|
||||
const needMigration = !this.dbService.db.docCustomPropertyInfo.get(id);
|
||||
const isBuiltIn =
|
||||
needMigration && BUILT_IN_CUSTOM_PROPERTY_TYPE.some(i => i.id === id);
|
||||
if (isBuiltIn) {
|
||||
this.createPropertyFromBuiltIn(id, config);
|
||||
} else if (needMigration) {
|
||||
// if this property is not in db, we need to migration it from legacy to db, only type and name is needed
|
||||
this.migrateLegacyDocPropertyInfo(id, config);
|
||||
} else {
|
||||
this.dbService.db.docCustomPropertyInfo.update(id, config);
|
||||
}
|
||||
}
|
||||
|
||||
migrateLegacyDocPropertyInfo(
|
||||
id: string,
|
||||
override: Partial<DocCustomPropertyInfo>
|
||||
) {
|
||||
const legacy = this.getLegacyDocPropertyInfo(id);
|
||||
this.dbService.db.docCustomPropertyInfo.create({
|
||||
id,
|
||||
type:
|
||||
legacy?.type ??
|
||||
'unknown' /* should never reach here, just for safety, we need handle unknown property type */,
|
||||
name: legacy?.name,
|
||||
...override,
|
||||
});
|
||||
}
|
||||
|
||||
createPropertyFromBuiltIn(
|
||||
id: string,
|
||||
override: Partial<DocCustomPropertyInfo>
|
||||
) {
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE.find(i => i.id === id);
|
||||
if (!builtIn) {
|
||||
return;
|
||||
}
|
||||
this.createDocPropertyInfo({ ...builtIn, ...override });
|
||||
}
|
||||
|
||||
watchDocPropertyInfoList() {
|
||||
return combineLatest([
|
||||
this.watchLegacyDocPropertyInfoList().pipe(
|
||||
map(this.upgradeLegacyDocPropertyInfoList)
|
||||
),
|
||||
this.dbService.db.docCustomPropertyInfo.find$(),
|
||||
]).pipe(
|
||||
map(([legacy, db]) => {
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
|
||||
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
|
||||
const all = [
|
||||
...withLegacy,
|
||||
...differenceBy(builtIn, withLegacy, i => i.id),
|
||||
];
|
||||
return all.filter(i => !i.isDeleted);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getDocProperties(id: string) {
|
||||
return {
|
||||
...this.upgradeLegacyDocProperties(this.getLegacyDocProperties(id)),
|
||||
@@ -137,28 +40,6 @@ export class DocPropertiesStore extends Store {
|
||||
};
|
||||
}
|
||||
|
||||
watchAllDocProperties() {
|
||||
const allDocProperties$ = this.dbService.db.docProperties.find$();
|
||||
const allLegacyDocProperties$ = this.watchAllLegacyDocProperties();
|
||||
|
||||
return combineLatest([allDocProperties$, allLegacyDocProperties$]).pipe(
|
||||
map(([db, legacy]) => {
|
||||
const map = new Map(db.map(i => [i.id, i]));
|
||||
const allIds = new Set([...map.keys(), ...Object.keys(legacy ?? {})]);
|
||||
|
||||
const result = {} as Record<string, Record<string, any>>;
|
||||
|
||||
for (const id of allIds) {
|
||||
result[id] = {
|
||||
...this.upgradeLegacyDocProperties(legacy?.[id]),
|
||||
...omitBy(map.get(id), isNil),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watchDocProperties(id: string) {
|
||||
return combineLatest([
|
||||
this.watchLegacyDocProperties(id).pipe(
|
||||
@@ -176,6 +57,20 @@ export class DocPropertiesStore extends Store {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* find doc ids by property key and value
|
||||
*
|
||||
* this apis will not include legacy properties
|
||||
*/
|
||||
watchPropertyAllValues(propertyKey: string) {
|
||||
return LiveData.from<Map<string, string | undefined>>(
|
||||
this.dbService.db.docProperties
|
||||
.select$(propertyKey)
|
||||
.pipe(map(o => new Map(o.map(i => [i.id, i[propertyKey]])))),
|
||||
new Map()
|
||||
);
|
||||
}
|
||||
|
||||
private upgradeLegacyDocProperties(properties?: LegacyDocProperties) {
|
||||
if (!properties) {
|
||||
return {};
|
||||
@@ -194,29 +89,6 @@ export class DocPropertiesStore extends Store {
|
||||
return newProperties;
|
||||
}
|
||||
|
||||
private upgradeLegacyDocPropertyInfoList(
|
||||
infoList?: LegacyDocPropertyInfoList
|
||||
) {
|
||||
if (!infoList) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newInfoList: DocCustomPropertyInfo[] = [];
|
||||
|
||||
for (const [id, info] of Object.entries(infoList ?? {})) {
|
||||
if (info?.type) {
|
||||
newInfoList.push({
|
||||
id,
|
||||
name: info.name,
|
||||
type: info.type,
|
||||
icon: info.icon,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newInfoList;
|
||||
}
|
||||
|
||||
private getLegacyDocProperties(id: string) {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
@@ -225,25 +97,8 @@ export class DocPropertiesStore extends Store {
|
||||
?.toJSON() as LegacyDocProperties | undefined;
|
||||
}
|
||||
|
||||
private watchAllLegacyDocProperties() {
|
||||
return yjsObserveByPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap<any>(
|
||||
'affine:workspace-properties'
|
||||
),
|
||||
`pageProperties`
|
||||
).pipe(
|
||||
switchMap(yjsObserveDeep),
|
||||
map(
|
||||
p =>
|
||||
(p instanceof YAbstractType ? p.toJSON() : p) as
|
||||
| { [docId: string]: LegacyDocProperties }
|
||||
| undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private watchLegacyDocProperties(id: string) {
|
||||
return yjsObserveByPath(
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap<any>(
|
||||
'affine:workspace-properties'
|
||||
),
|
||||
@@ -258,40 +113,4 @@ export class DocPropertiesStore extends Store {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private getLegacyDocPropertyInfoList() {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
.get('schema')
|
||||
?.get('pageProperties')
|
||||
?.get('custom')
|
||||
?.toJSON() as LegacyDocPropertyInfoList | undefined;
|
||||
}
|
||||
|
||||
private watchLegacyDocPropertyInfoList() {
|
||||
return yjsObserveByPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap<any>(
|
||||
'affine:workspace-properties'
|
||||
),
|
||||
'schema.pageProperties.custom'
|
||||
).pipe(
|
||||
switchMap(yjsObserveDeep),
|
||||
map(
|
||||
p =>
|
||||
(p instanceof YAbstractType ? p.toJSON() : p) as
|
||||
| LegacyDocPropertyInfoList
|
||||
| undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private getLegacyDocPropertyInfo(id: string) {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
.get('schema')
|
||||
?.get('pageProperties')
|
||||
?.get('custom')
|
||||
?.get(id)
|
||||
?.toJSON() as LegacyDocPropertyInfo | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { DocMode } from '@blocksuite/affine/model';
|
||||
import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import {
|
||||
Store,
|
||||
yjsGetPath,
|
||||
yjsObserve,
|
||||
yjsObserveByPath,
|
||||
yjsObserveDeep,
|
||||
yjsObservePath,
|
||||
} from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { distinctUntilChanged, map, switchMap } from 'rxjs';
|
||||
@@ -63,7 +64,7 @@ export class DocsStore extends Store {
|
||||
}
|
||||
|
||||
watchDocIds() {
|
||||
return yjsObserveByPath(
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
@@ -78,8 +79,65 @@ export class DocsStore extends Store {
|
||||
);
|
||||
}
|
||||
|
||||
watchAllDocUpdatedDate() {
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
switchMap(pages => yjsObservePath(pages, '*.updatedDate')),
|
||||
map(pages => {
|
||||
if (pages instanceof YArray) {
|
||||
return pages.map(v => ({
|
||||
id: v.get('id') as string,
|
||||
updatedDate: v.get('updatedDate') as number | undefined,
|
||||
}));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watchAllDocTagIds() {
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
switchMap(pages => yjsObservePath(pages, '*.tags')),
|
||||
map(pages => {
|
||||
if (pages instanceof YArray) {
|
||||
return pages.map(v => ({
|
||||
id: v.get('id') as string,
|
||||
tags: (v.get('tags')?.toJSON() ?? []) as string[],
|
||||
}));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watchAllDocCreateDate() {
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
switchMap(pages => yjsObservePath(pages, '*.createDate')),
|
||||
map(pages => {
|
||||
if (pages instanceof YArray) {
|
||||
return pages.map(v => ({
|
||||
id: v.get('id') as string,
|
||||
createDate: (v.get('createDate') ?? 0) as number,
|
||||
}));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watchNonTrashDocIds() {
|
||||
return yjsObserveByPath(
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
@@ -97,7 +155,7 @@ export class DocsStore extends Store {
|
||||
}
|
||||
|
||||
watchTrashDocIds() {
|
||||
return yjsObserveByPath(
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
@@ -116,7 +174,7 @@ export class DocsStore extends Store {
|
||||
|
||||
watchDocMeta(id: string) {
|
||||
let docMetaIndexCache = -1;
|
||||
return yjsObserveByPath(
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
|
||||
import type { DefaultOpenProperty } from '@affine/core/components/doc-properties';
|
||||
import type { DefaultOpenProperty } from '@affine/core/components/properties';
|
||||
import { PresentTool } from '@blocksuite/affine/blocks/frame';
|
||||
import { DefaultTool } from '@blocksuite/affine/blocks/surface';
|
||||
import type { DocTitle } from '@blocksuite/affine/fragments/doc-title';
|
||||
|
||||
@@ -11,6 +11,7 @@ import { configAtMenuConfigModule } from './at-menu-config';
|
||||
import { configureBlobManagementModule } from './blob-management';
|
||||
import { configureCloudModule } from './cloud';
|
||||
import { configureCollectionModule } from './collection';
|
||||
import { configureCollectionRulesModule } from './collection-rules';
|
||||
import { configureWorkspaceDBModule } from './db';
|
||||
import { configureDialogModule } from './dialogs';
|
||||
import { configureDndModule } from './dnd';
|
||||
@@ -56,6 +57,7 @@ import { configureThemeEditorModule } from './theme-editor';
|
||||
import { configureUrlModule } from './url';
|
||||
import { configureUserspaceModule } from './userspace';
|
||||
import { configureWorkspaceModule } from './workspace';
|
||||
import { configureWorkspacePropertyModule } from './workspace-property';
|
||||
|
||||
export function configureCommonModules(framework: Framework) {
|
||||
configureI18nModule(framework);
|
||||
@@ -110,4 +112,6 @@ export function configureCommonModules(framework: Framework) {
|
||||
configureImportClipperModule(framework);
|
||||
configureNotificationModule(framework);
|
||||
configureIntegrationModule(framework);
|
||||
configureWorkspacePropertyModule(framework);
|
||||
configureCollectionRulesModule(framework);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DefaultOpenProperty } from '@affine/core/components/doc-properties';
|
||||
import type { DefaultOpenProperty } from '@affine/core/components/properties';
|
||||
import type { DocMode } from '@blocksuite/affine/model';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
@@ -66,6 +66,7 @@ export class MemberSearchService extends Service {
|
||||
}
|
||||
|
||||
search(searchText?: string) {
|
||||
console.log('search', searchText);
|
||||
this.reset();
|
||||
this.searchText$.setValue(searchText ?? '');
|
||||
this.loadMore();
|
||||
|
||||
@@ -262,12 +262,12 @@ export class SearchMenuService extends Service {
|
||||
query,
|
||||
}),
|
||||
items: result.map(item => {
|
||||
const title = this.highlightFuseTitle(
|
||||
const name = this.highlightFuseTitle(
|
||||
item.matches,
|
||||
item.item.title,
|
||||
'title'
|
||||
item.item.name,
|
||||
'name'
|
||||
);
|
||||
return this.toTagMenuItem({ ...item.item, title }, action);
|
||||
return this.toTagMenuItem({ ...item.item, name }, action);
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -285,7 +285,7 @@ export class SearchMenuService extends Service {
|
||||
`;
|
||||
return {
|
||||
key: tag.id,
|
||||
name: html`${unsafeHTML(tag.title)}`,
|
||||
name: html`${unsafeHTML(tag.name)}`,
|
||||
icon: tagIcon,
|
||||
action: async () => {
|
||||
await action(tag);
|
||||
|
||||
@@ -58,7 +58,7 @@ export class TagList extends Entity {
|
||||
return get(this.tags$).map(tag => {
|
||||
return {
|
||||
id: tag.id,
|
||||
title: get(tag.value$),
|
||||
name: get(tag.value$),
|
||||
color: get(tag.color$),
|
||||
createDate: get(tag.createDate$),
|
||||
updatedDate: get(tag.updateDate$),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Tag, Tag as TagSchema } from '@affine/env/filter';
|
||||
import type { DocsPropertiesMeta } from '@blocksuite/affine/store';
|
||||
import {
|
||||
LiveData,
|
||||
Store,
|
||||
yjsObserveByPath,
|
||||
yjsObserveDeep,
|
||||
yjsGetPath,
|
||||
yjsObservePath,
|
||||
} from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { map, Observable, switchMap } from 'rxjs';
|
||||
@@ -12,6 +11,15 @@ import { Array as YArray } from 'yjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
export type Tag = {
|
||||
value: string;
|
||||
id: string;
|
||||
color: string;
|
||||
createDate?: number | Date | undefined;
|
||||
updateDate?: number | Date | undefined;
|
||||
parentId?: string | undefined;
|
||||
};
|
||||
|
||||
export class TagStore extends Store {
|
||||
get properties() {
|
||||
return this.workspaceService.workspace.docCollection.meta.properties;
|
||||
@@ -105,13 +113,13 @@ export class TagStore extends Store {
|
||||
|
||||
watchTagInfo(id: string) {
|
||||
return this.tagOptions$.map(
|
||||
tags => tags.find(tag => tag.id === id) as TagSchema | undefined
|
||||
tags => tags.find(tag => tag.id === id) as Tag | undefined
|
||||
);
|
||||
}
|
||||
|
||||
updateTagInfo(id: string, tagInfo: Partial<TagSchema>) {
|
||||
updateTagInfo(id: string, tagInfo: Partial<Tag>) {
|
||||
const tag = this.tagOptions$.value.find(tag => tag.id === id) as
|
||||
| TagSchema
|
||||
| Tag
|
||||
| undefined;
|
||||
if (!tag) {
|
||||
return;
|
||||
@@ -127,11 +135,13 @@ export class TagStore extends Store {
|
||||
}
|
||||
|
||||
watchTagPageIds(id: string) {
|
||||
return yjsObserveByPath(
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
switchMap(yjsObserveDeep),
|
||||
switchMap(pages => {
|
||||
return yjsObservePath(pages, '*.tags');
|
||||
}),
|
||||
map(meta => {
|
||||
if (meta instanceof YArray) {
|
||||
return meta
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceDBService } from '../db';
|
||||
import { WorkspaceService } from '../workspace';
|
||||
import { WorkspaceScope } from '../workspace/scopes/workspace';
|
||||
import { WorkspacePropertyService } from './services/workspace-property';
|
||||
import { WorkspacePropertyStore } from './stores/workspace-property';
|
||||
|
||||
export { WorkspacePropertyService } from './services/workspace-property';
|
||||
export type * from './types';
|
||||
|
||||
export function configureWorkspacePropertyModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(WorkspacePropertyService, [WorkspacePropertyStore])
|
||||
.store(WorkspacePropertyStore, [WorkspaceService, WorkspaceDBService]);
|
||||
}
|
||||
+10
-8
@@ -1,19 +1,21 @@
|
||||
import {
|
||||
Entity,
|
||||
generateFractionalIndexingKeyBetween,
|
||||
LiveData,
|
||||
Service,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import type { DocCustomPropertyInfo } from '../../db/schema/schema';
|
||||
import type { DocPropertiesStore } from '../stores/doc-properties';
|
||||
import type { WorkspacePropertyStore } from '../stores/workspace-property';
|
||||
|
||||
export class DocPropertyList extends Entity {
|
||||
constructor(private readonly docPropertiesStore: DocPropertiesStore) {
|
||||
export class WorkspacePropertyService extends Service {
|
||||
constructor(
|
||||
private readonly workspacePropertiesStore: WorkspacePropertyStore
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
properties$ = LiveData.from(
|
||||
this.docPropertiesStore.watchDocPropertyInfoList(),
|
||||
this.workspacePropertiesStore.watchWorkspaceProperties(),
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -27,17 +29,17 @@ export class DocPropertyList extends Entity {
|
||||
}
|
||||
|
||||
updatePropertyInfo(id: string, properties: Partial<DocCustomPropertyInfo>) {
|
||||
this.docPropertiesStore.updateDocPropertyInfo(id, properties);
|
||||
this.workspacePropertiesStore.updateWorkspaceProperty(id, properties);
|
||||
}
|
||||
|
||||
createProperty(
|
||||
properties: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
|
||||
) {
|
||||
return this.docPropertiesStore.createDocPropertyInfo(properties);
|
||||
return this.workspacePropertiesStore.createWorkspaceProperty(properties);
|
||||
}
|
||||
|
||||
removeProperty(id: string) {
|
||||
this.docPropertiesStore.removeDocPropertyInfo(id);
|
||||
this.workspacePropertiesStore.removeWorkspaceProperty(id);
|
||||
}
|
||||
|
||||
indexAt(at: 'before' | 'after', targetId?: string) {
|
||||
@@ -0,0 +1,175 @@
|
||||
import { Store, yjsGetPath, yjsObserveDeep } from '@toeverything/infra';
|
||||
import { differenceBy } from 'lodash-es';
|
||||
import { combineLatest, map, switchMap } from 'rxjs';
|
||||
import { AbstractType as YAbstractType } from 'yjs';
|
||||
|
||||
import type { WorkspaceDBService } from '../../db';
|
||||
import type { DocCustomPropertyInfo } from '../../db/schema/schema';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { BUILT_IN_CUSTOM_PROPERTY_TYPE } from '../constants';
|
||||
import type { WorkspacePropertyType } from '../types';
|
||||
|
||||
type LegacyWorkspacePropertyInfo = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
type LegacyWorkspacePropertyInfoList = Record<
|
||||
string,
|
||||
LegacyWorkspacePropertyInfo | undefined
|
||||
>;
|
||||
|
||||
export class WorkspacePropertyStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly dbService: WorkspaceDBService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getWorkspaceProperties() {
|
||||
const db = this.dbService.db.docCustomPropertyInfo.find();
|
||||
const legacy = this.upgradeLegacyWorkspacePropertyInfoList(
|
||||
this.getLegacyWorkspacePropertyInfoList()
|
||||
);
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
|
||||
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
|
||||
const all = [
|
||||
...withLegacy,
|
||||
...differenceBy(builtIn, withLegacy, i => i.id),
|
||||
];
|
||||
return all.filter(i => !i.isDeleted);
|
||||
}
|
||||
|
||||
createWorkspaceProperty(
|
||||
config: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
|
||||
) {
|
||||
return this.dbService.db.docCustomPropertyInfo.create(config);
|
||||
}
|
||||
|
||||
removeWorkspaceProperty(id: string) {
|
||||
this.updateWorkspaceProperty(id, {
|
||||
additionalData: {}, // also remove additional data to reduce size
|
||||
isDeleted: true,
|
||||
});
|
||||
}
|
||||
|
||||
updateWorkspaceProperty(id: string, config: Partial<DocCustomPropertyInfo>) {
|
||||
const needMigration = !this.dbService.db.docCustomPropertyInfo.get(id);
|
||||
const isBuiltIn =
|
||||
needMigration && BUILT_IN_CUSTOM_PROPERTY_TYPE.some(i => i.id === id);
|
||||
if (isBuiltIn) {
|
||||
this.createWorkspacePropertyFromBuiltIn(id, config);
|
||||
} else if (needMigration) {
|
||||
// if this property is not in db, we need to migration it from legacy to db, only type and name is needed
|
||||
this.migrateLegacyWorkspaceProperty(id, config);
|
||||
} else {
|
||||
this.dbService.db.docCustomPropertyInfo.update(id, config);
|
||||
}
|
||||
}
|
||||
|
||||
migrateLegacyWorkspaceProperty(
|
||||
id: string,
|
||||
override: Partial<DocCustomPropertyInfo>
|
||||
) {
|
||||
const legacy = this.getLegacyWorkspacePropertyInfo(id);
|
||||
this.dbService.db.docCustomPropertyInfo.create({
|
||||
id,
|
||||
type: (legacy?.type ??
|
||||
'unknown') /* should never reach here, just for safety, we need handle unknown property type */ as WorkspacePropertyType,
|
||||
name: legacy?.name,
|
||||
...override,
|
||||
});
|
||||
}
|
||||
|
||||
createWorkspacePropertyFromBuiltIn(
|
||||
id: string,
|
||||
override: Partial<DocCustomPropertyInfo>
|
||||
) {
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE.find(i => i.id === id);
|
||||
if (!builtIn) {
|
||||
return;
|
||||
}
|
||||
this.createWorkspaceProperty({ ...builtIn, ...override });
|
||||
}
|
||||
|
||||
watchWorkspaceProperties() {
|
||||
return combineLatest([
|
||||
this.watchLegacyWorkspacePropertyInfoList().pipe(
|
||||
map(this.upgradeLegacyWorkspacePropertyInfoList)
|
||||
),
|
||||
this.dbService.db.docCustomPropertyInfo.find$(),
|
||||
]).pipe(
|
||||
map(([legacy, db]) => {
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
|
||||
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
|
||||
const all = [
|
||||
...withLegacy,
|
||||
...differenceBy(builtIn, withLegacy, i => i.id),
|
||||
];
|
||||
return all.filter(i => !i.isDeleted);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private upgradeLegacyWorkspacePropertyInfoList(
|
||||
infoList?: LegacyWorkspacePropertyInfoList
|
||||
) {
|
||||
if (!infoList) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newInfoList: DocCustomPropertyInfo[] = [];
|
||||
|
||||
for (const [id, info] of Object.entries(infoList ?? {})) {
|
||||
if (info?.type) {
|
||||
newInfoList.push({
|
||||
id,
|
||||
name: info.name,
|
||||
type: info.type as WorkspacePropertyType,
|
||||
icon: info.icon,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newInfoList;
|
||||
}
|
||||
|
||||
private getLegacyWorkspacePropertyInfoList() {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
.get('schema')
|
||||
?.get('pageProperties')
|
||||
?.get('custom')
|
||||
?.toJSON() as LegacyWorkspacePropertyInfoList | undefined;
|
||||
}
|
||||
|
||||
private watchLegacyWorkspacePropertyInfoList() {
|
||||
return yjsGetPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap<any>(
|
||||
'affine:workspace-properties'
|
||||
),
|
||||
'schema.pageProperties.custom'
|
||||
).pipe(
|
||||
switchMap(yjsObserveDeep),
|
||||
map(
|
||||
p =>
|
||||
(p instanceof YAbstractType ? p.toJSON() : p) as
|
||||
| LegacyWorkspacePropertyInfoList
|
||||
| undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private getLegacyWorkspacePropertyInfo(id: string) {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
.get('schema')
|
||||
?.get('pageProperties')
|
||||
?.get('custom')
|
||||
?.get(id)
|
||||
?.toJSON() as LegacyWorkspacePropertyInfo | undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
type DateFilters =
|
||||
| 'after'
|
||||
| 'before'
|
||||
| 'between'
|
||||
| 'last-3-days'
|
||||
| 'last-7-days'
|
||||
| 'last-15-days'
|
||||
| 'last-30-days'
|
||||
| 'this-month'
|
||||
| 'this-week'
|
||||
| 'this-quarter'
|
||||
| 'this-year';
|
||||
|
||||
export type WorkspacePropertyTypes = {
|
||||
tags: {
|
||||
filter: 'include' | 'is-not-empty' | 'is-empty';
|
||||
};
|
||||
text: {
|
||||
filter: 'is' | 'is-not' | 'is-not-empty' | 'is-empty';
|
||||
};
|
||||
number: {
|
||||
filter: 'is' | 'is-not' | 'is-not-empty' | 'is-empty';
|
||||
};
|
||||
checkbox: {
|
||||
filter: 'is' | 'is-not';
|
||||
};
|
||||
date: {
|
||||
filter: DateFilters | 'is-not-empty' | 'is-empty';
|
||||
};
|
||||
createdBy: { filter: 'include' };
|
||||
updatedBy: { filter: 'include' };
|
||||
updatedAt: { filter: DateFilters };
|
||||
createdAt: { filter: DateFilters };
|
||||
docPrimaryMode: { filter: 'is' | 'is-not' };
|
||||
journal: { filter: 'is' | 'is-not' };
|
||||
edgelessTheme: { filter: never };
|
||||
pageWidth: { filter: never };
|
||||
template: { filter: never };
|
||||
unknown: { filter: never };
|
||||
};
|
||||
export type WorkspacePropertyType = keyof WorkspacePropertyTypes;
|
||||
|
||||
export type WorkspacePropertyFilter<T extends WorkspacePropertyType> =
|
||||
WorkspacePropertyTypes[T]['filter'];
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Workspace as WorkspaceInterface } from '@blocksuite/affine/store';
|
||||
import { Entity, LiveData, yjsObserveByPath } from '@toeverything/infra';
|
||||
import { Entity, LiveData, yjsGetPath } from '@toeverything/infra';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { Doc as YDoc, transact } from 'yjs';
|
||||
|
||||
@@ -80,14 +80,14 @@ export class Workspace extends Entity {
|
||||
}
|
||||
|
||||
name$ = LiveData.from<string | undefined>(
|
||||
yjsObserveByPath(this.rootYDoc.getMap('meta'), 'name') as Observable<
|
||||
yjsGetPath(this.rootYDoc.getMap('meta'), 'name') as Observable<
|
||||
string | undefined
|
||||
>,
|
||||
undefined
|
||||
);
|
||||
|
||||
avatar$ = LiveData.from(
|
||||
yjsObserveByPath(this.rootYDoc.getMap('meta'), 'avatar') as Observable<
|
||||
yjsGetPath(this.rootYDoc.getMap('meta'), 'avatar') as Observable<
|
||||
string | undefined
|
||||
>,
|
||||
undefined
|
||||
|
||||
Reference in New Issue
Block a user