feat(core): add more collection rules (#12458)

This commit is contained in:
EYHN
2025-05-23 16:46:02 +09:00
committed by GitHub
parent 0902b2b9c9
commit ac4954f7ad
28 changed files with 1170 additions and 38 deletions

View File

@@ -0,0 +1,5 @@
export {
EdgelessThemeDocListProperty,
EdgelessThemeFilterValue,
EdgelessThemeGroupHeader,
} from '../workspace-property-types/edgeless-theme';

View File

@@ -3,11 +3,15 @@ import type { DocRecord } from '@affine/core/modules/doc';
import type { I18nString } from '@affine/i18n';
import {
DateTimeIcon,
EdgelessIcon,
FavoriteIcon,
HistoryIcon,
IntegrationsIcon,
LongerIcon,
MemberIcon,
ShareIcon,
TagIcon,
TemplateIcon,
} from '@blocksuite/icons/rc';
import type { GroupHeaderProps } from '../explorer/types';
@@ -26,9 +30,29 @@ import {
ModifiedByGroupHeader,
UpdatedByDocListInlineProperty,
} from './created-updated-by';
import {
EdgelessThemeDocListProperty,
EdgelessThemeFilterValue,
EdgelessThemeGroupHeader,
} from './edgeless-theme';
import { FavoriteFilterValue } from './favorite';
import {
IntegrationTypeDocListProperty,
IntegrationTypeFilterValue,
IntegrationTypeGroupHeader,
} from './integration-type';
import {
PageWidthDocListProperty,
PageWidthFilterValue,
PageWidthGroupHeader,
} from './page-width';
import { SharedFilterValue } from './shared';
import { TagsDocListProperty, TagsFilterValue, TagsGroupHeader } from './tags';
import {
TemplateDocListProperty,
TemplateFilterValue,
TemplateGroupHeader,
} from './template';
export const SystemPropertyTypes = {
tags: {
@@ -122,6 +146,66 @@ export const SystemPropertyTypes = {
},
filterValue: SharedFilterValue,
},
edgelessTheme: {
icon: EdgelessIcon,
name: 'com.affine.page-properties.property.edgelessTheme',
showInDocList: 'stack',
allowInGroupBy: true,
allowInOrderBy: true,
docListProperty: EdgelessThemeDocListProperty,
groupHeader: EdgelessThemeGroupHeader,
filterMethod: {
is: 'com.affine.editCollection.rules.include.is',
'is-not': 'com.affine.editCollection.rules.include.is-not',
},
filterValue: EdgelessThemeFilterValue,
defaultFilter: { method: 'is', value: 'system' },
},
pageWidth: {
icon: LongerIcon,
name: 'com.affine.page-properties.property.pageWidth',
showInDocList: 'stack',
allowInGroupBy: true,
allowInOrderBy: true,
docListProperty: PageWidthDocListProperty,
groupHeader: PageWidthGroupHeader,
filterMethod: {
is: 'com.affine.editCollection.rules.include.is',
'is-not': 'com.affine.editCollection.rules.include.is-not',
},
filterValue: PageWidthFilterValue,
defaultFilter: { method: 'is', value: 'fullWidth' },
},
template: {
icon: TemplateIcon,
name: 'com.affine.page-properties.property.template',
showInDocList: 'stack',
allowInGroupBy: true,
allowInOrderBy: true,
docListProperty: TemplateDocListProperty,
groupHeader: TemplateGroupHeader,
filterMethod: {
is: 'com.affine.editCollection.rules.include.is',
'is-not': 'com.affine.editCollection.rules.include.is-not',
},
filterValue: TemplateFilterValue,
defaultFilter: { method: 'is', value: 'true' },
},
integrationType: {
icon: IntegrationsIcon,
name: 'com.affine.integration.integrations',
showInDocList: 'stack',
allowInGroupBy: true,
allowInOrderBy: true,
docListProperty: IntegrationTypeDocListProperty,
groupHeader: IntegrationTypeGroupHeader,
filterMethod: {
is: 'com.affine.editCollection.rules.include.is',
'is-not': 'com.affine.editCollection.rules.include.is-not',
},
filterValue: IntegrationTypeFilterValue,
defaultFilter: { method: 'is', value: 'readwise' },
},
} as {
[type: string]: {
icon: React.FC<React.SVGProps<SVGSVGElement>>;

View File

@@ -0,0 +1,98 @@
import { Menu, MenuItem, type MenuRef } from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules';
import type { DocRecord } from '@affine/core/modules/doc';
import { useI18n } from '@affine/i18n';
import { IntegrationsIcon, ReadwiseIcon } from '@blocksuite/icons/rc';
import { useLiveData } from '@toeverything/infra';
import { useEffect, useRef } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property';
import type { GroupHeaderProps } from '../explorer/types';
export const IntegrationTypeFilterValue = ({
filter,
isDraft,
onDraftCompleted,
onChange,
}: {
filter: FilterParams;
isDraft?: boolean;
onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void;
}) => {
const t = useI18n();
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return (
<Menu
ref={menuRef}
rootOptions={{
onClose: onDraftCompleted,
}}
items={
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'readwise',
});
}}
prefixIcon={<ReadwiseIcon />}
selected={filter.value === 'readwise'}
>
{t['com.affine.integration.readwise.name']()}
</MenuItem>
}
>
<span>
{filter.value === 'readwise'
? t['com.affine.integration.readwise.name']()
: filter.value}
</span>
</Menu>
);
};
export const IntegrationTypeDocListProperty = ({ doc }: { doc: DocRecord }) => {
const integrationType = useLiveData(doc.property$('integrationType'));
if (!integrationType) {
return null;
}
return (
<StackProperty
icon={
integrationType === 'readwise' ? <ReadwiseIcon /> : <IntegrationsIcon />
}
>
{integrationType}
</StackProperty>
);
};
export const IntegrationTypeGroupHeader = ({
groupId,
docCount,
}: GroupHeaderProps) => {
const t = useI18n();
const text =
groupId === 'readwise'
? t['com.affine.integration.readwise.name']()
: groupId
? groupId
: 'No integrations';
return (
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
{text}
</PlainTextDocGroupHeader>
);
};

View File

@@ -0,0 +1,5 @@
export {
PageWidthDocListProperty,
PageWidthFilterValue,
PageWidthGroupHeader,
} from '../workspace-property-types/page-width';

View File

@@ -0,0 +1,5 @@
export {
TemplateDocListProperty,
TemplateFilterValue,
TemplateGroupHeader,
} from '../workspace-property-types/template';

View File

@@ -123,7 +123,11 @@ export const CheckboxGroupHeader = ({
groupId,
docCount,
}: GroupHeaderProps) => {
const text = groupId === 'true' ? 'Checked' : 'Unchecked';
const t = useI18n();
const text =
groupId === 'true'
? t['com.affine.all-docs.group.is-checked']()
: t['com.affine.all-docs.group.is-not-checked']();
return (
<PlainTextDocGroupHeader docCount={docCount} groupId={groupId}>
{text}

View File

@@ -1,12 +1,20 @@
import { PropertyValue, type RadioItem } from '@affine/component';
import { DocService } from '@affine/core/modules/doc';
import {
Menu,
MenuItem,
type MenuRef,
PropertyValue,
type RadioItem,
} from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules';
import { type DocRecord, DocService } from '@affine/core/modules/doc';
import { useI18n } from '@affine/i18n';
import { EdgelessIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property';
import type { DocListPropertyProps } from '../explorer/types';
import type { GroupHeaderProps } from '../explorer/types';
import type { PropertyValueProps } from '../properties/types';
import { PropertyRadioGroup } from '../properties/widgets/radio-group';
import * as styles from './edgeless-theme.css';
@@ -60,7 +68,7 @@ export const EdgelessThemeValue = ({
);
};
export const EdgelessThemeDocListProperty = ({ doc }: DocListPropertyProps) => {
export const EdgelessThemeDocListProperty = ({ doc }: { doc: DocRecord }) => {
const t = useI18n();
const edgelessTheme = useLiveData(
doc.properties$.selector(p => p.edgelessColorTheme)
@@ -76,3 +84,99 @@ export const EdgelessThemeDocListProperty = ({ doc }: DocListPropertyProps) => {
</StackProperty>
);
};
export const EdgelessThemeFilterValue = ({
filter,
isDraft,
onDraftCompleted,
onChange,
}: {
filter: FilterParams;
isDraft?: boolean;
onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void;
}) => {
const t = useI18n();
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return (
<Menu
ref={menuRef}
rootOptions={{
onClose: onDraftCompleted,
}}
items={
<>
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'system',
});
}}
selected={filter.value === 'system'}
>
{t['com.affine.themeSettings.auto']()}
</MenuItem>
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'light',
});
}}
selected={filter.value === 'light'}
>
{t['com.affine.themeSettings.light']()}
</MenuItem>
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'dark',
});
}}
selected={filter.value === 'dark'}
>
{t['com.affine.themeSettings.dark']()}
</MenuItem>
</>
}
>
<span>
{filter.value === 'system'
? t['com.affine.themeSettings.auto']()
: filter.value === 'light'
? t['com.affine.themeSettings.light']()
: t['com.affine.themeSettings.dark']()}
</span>
</Menu>
);
};
export const EdgelessThemeGroupHeader = ({
groupId,
docCount,
}: GroupHeaderProps) => {
const t = useI18n();
const text =
groupId === 'light'
? t['com.affine.themeSettings.light']()
: groupId === 'dark'
? t['com.affine.themeSettings.dark']()
: groupId === 'system'
? t['com.affine.themeSettings.auto']()
: 'Default';
return (
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
{text}
</PlainTextDocGroupHeader>
);
};

View File

@@ -60,6 +60,8 @@ import {
} from './doc-primary-mode';
import {
EdgelessThemeDocListProperty,
EdgelessThemeFilterValue,
EdgelessThemeGroupHeader,
EdgelessThemeValue,
} from './edgeless-theme';
import {
@@ -69,14 +71,24 @@ import {
JournalValue,
} from './journal';
import { NumberDocListProperty, NumberValue } from './number';
import { PageWidthDocListProperty, PageWidthValue } from './page-width';
import {
PageWidthDocListProperty,
PageWidthFilterValue,
PageWidthGroupHeader,
PageWidthValue,
} from './page-width';
import {
TagsDocListProperty,
TagsFilterValue,
TagsGroupHeader,
TagsValue,
} from './tags';
import { TemplateDocListProperty, TemplateValue } from './template';
import {
TemplateDocListProperty,
TemplateFilterValue,
TemplateGroupHeader,
TemplateValue,
} from './template';
import {
TextDocListProperty,
TextFilterValue,
@@ -290,7 +302,16 @@ export const WorkspacePropertyTypes = {
name: 'com.affine.page-properties.property.edgelessTheme',
description: 'com.affine.page-properties.property.edgelessTheme.tooltips',
showInDocList: 'stack',
allowInGroupBy: true,
allowInOrderBy: true,
docListProperty: EdgelessThemeDocListProperty,
groupHeader: EdgelessThemeGroupHeader,
filterMethod: {
is: 'com.affine.editCollection.rules.include.is',
'is-not': 'com.affine.editCollection.rules.include.is-not',
},
filterValue: EdgelessThemeFilterValue,
defaultFilter: { method: 'is', value: 'system' },
},
pageWidth: {
icon: LongerIcon,
@@ -298,7 +319,16 @@ export const WorkspacePropertyTypes = {
name: 'com.affine.page-properties.property.pageWidth',
description: 'com.affine.page-properties.property.pageWidth.tooltips',
showInDocList: 'stack',
allowInGroupBy: true,
allowInOrderBy: true,
docListProperty: PageWidthDocListProperty,
groupHeader: PageWidthGroupHeader,
filterMethod: {
is: 'com.affine.editCollection.rules.include.is',
'is-not': 'com.affine.editCollection.rules.include.is-not',
},
filterValue: PageWidthFilterValue,
defaultFilter: { method: 'is', value: 'fullWidth' },
},
template: {
icon: TemplateIcon,
@@ -307,7 +337,16 @@ export const WorkspacePropertyTypes = {
renameable: true,
description: 'com.affine.page-properties.property.template.tooltips',
showInDocList: 'stack',
allowInGroupBy: true,
allowInOrderBy: true,
docListProperty: TemplateDocListProperty,
groupHeader: TemplateGroupHeader,
filterMethod: {
is: 'com.affine.editCollection.rules.include.is',
'is-not': 'com.affine.editCollection.rules.include.is-not',
},
filterValue: TemplateFilterValue,
defaultFilter: { method: 'is', value: 'true' },
},
unknown: {
icon: PropertyIcon,

View File

@@ -254,7 +254,11 @@ export const JournalDocListProperty = ({ doc }: DocListPropertyProps) => {
};
export const JournalGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
const text = groupId === 'true' ? 'Journal' : 'Not Journal';
const t = useI18n();
const text =
groupId === 'true'
? t['com.affine.all-docs.group.is-journal']()
: t['com.affine.all-docs.group.is-not-journal']();
return (
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
{text}

View File

@@ -1,13 +1,21 @@
import { PropertyValue, type RadioItem } from '@affine/component';
import { DocService } from '@affine/core/modules/doc';
import {
Menu,
MenuItem,
type MenuRef,
PropertyValue,
type RadioItem,
} from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules';
import { type DocRecord, DocService } from '@affine/core/modules/doc';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { useI18n } from '@affine/i18n';
import { LongerIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property';
import type { DocListPropertyProps } from '../explorer/types';
import type { GroupHeaderProps } from '../explorer/types';
import type { PropertyValueProps } from '../properties/types';
import { PropertyRadioGroup } from '../properties/widgets/radio-group';
import { container } from './page-width.css';
@@ -62,7 +70,7 @@ export const PageWidthValue = ({ readonly }: PropertyValueProps) => {
);
};
export const PageWidthDocListProperty = ({ doc }: DocListPropertyProps) => {
export const PageWidthDocListProperty = ({ doc }: { doc: DocRecord }) => {
const t = useI18n();
const pageWidth = useLiveData(doc.properties$.selector(p => p.pageWidth));
@@ -78,3 +86,96 @@ export const PageWidthDocListProperty = ({ doc }: DocListPropertyProps) => {
</StackProperty>
);
};
export const PageWidthFilterValue = ({
filter,
isDraft,
onDraftCompleted,
onChange,
}: {
filter: FilterParams;
isDraft?: boolean;
onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void;
}) => {
const t = useI18n();
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return (
<Menu
ref={menuRef}
rootOptions={{
onClose: onDraftCompleted,
}}
items={
<>
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'fullWidth',
});
}}
selected={filter.value === 'fullWidth'}
>
{t[
'com.affine.settings.editorSettings.page.default-page-width.full-width'
]()}
</MenuItem>
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'standard',
});
}}
selected={filter.value !== 'fullWidth'}
>
{t[
'com.affine.settings.editorSettings.page.default-page-width.standard'
]()}
</MenuItem>
</>
}
>
<span>
{filter.value === 'fullWidth'
? t[
'com.affine.settings.editorSettings.page.default-page-width.full-width'
]()
: t[
'com.affine.settings.editorSettings.page.default-page-width.standard'
]()}
</span>
</Menu>
);
};
export const PageWidthGroupHeader = ({
groupId,
docCount,
}: GroupHeaderProps) => {
const t = useI18n();
const text =
groupId === 'fullWidth'
? t[
'com.affine.settings.editorSettings.page.default-page-width.full-width'
]()
: groupId === 'standard'
? t[
'com.affine.settings.editorSettings.page.default-page-width.standard'
]()
: 'Default';
return (
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
{text}
</PlainTextDocGroupHeader>
);
};

View File

@@ -1,12 +1,20 @@
import { Checkbox, PropertyValue } from '@affine/component';
import { DocService } from '@affine/core/modules/doc';
import {
Checkbox,
Menu,
MenuItem,
type MenuRef,
PropertyValue,
} from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules';
import { type DocRecord, DocService } from '@affine/core/modules/doc';
import { useI18n } from '@affine/i18n';
import { TemplateIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { type ChangeEvent, useCallback } from 'react';
import { type ChangeEvent, useCallback, useEffect, useRef } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property';
import type { DocListPropertyProps } from '../explorer/types';
import type { GroupHeaderProps } from '../explorer/types';
import type { PropertyValueProps } from '../properties/types';
import * as styles from './template.css';
@@ -44,7 +52,7 @@ export const TemplateValue = ({ readonly }: PropertyValueProps) => {
);
};
export const TemplateDocListProperty = ({ doc }: DocListPropertyProps) => {
export const TemplateDocListProperty = ({ doc }: { doc: DocRecord }) => {
const t = useI18n();
const isTemplate = useLiveData(doc.properties$.selector(p => p.isTemplate));
@@ -56,3 +64,79 @@ export const TemplateDocListProperty = ({ doc }: DocListPropertyProps) => {
<StackProperty icon={<TemplateIcon />}>{t['Template']()}</StackProperty>
);
};
export const TemplateGroupHeader = ({
groupId,
docCount,
}: GroupHeaderProps) => {
const t = useI18n();
const text =
groupId === 'true'
? t['com.affine.all-docs.group.is-template']()
: groupId === 'false'
? t['com.affine.all-docs.group.is-not-template']()
: 'Default';
return (
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
{text}
</PlainTextDocGroupHeader>
);
};
export const TemplateFilterValue = ({
filter,
isDraft,
onDraftCompleted,
onChange,
}: {
filter: FilterParams;
isDraft?: boolean;
onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void;
}) => {
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return (
<Menu
ref={menuRef}
rootOptions={{
onClose: onDraftCompleted,
}}
items={
<>
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'true',
});
}}
selected={filter.value === 'true'}
>
{'True'}
</MenuItem>
<MenuItem
onClick={() => {
onChange?.({
...filter,
value: 'false',
});
}}
selected={filter.value !== 'true'}
>
{'False'}
</MenuItem>
</>
}
>
<span>{filter.value === 'true' ? 'True' : 'False'}</span>
</Menu>
);
};

View File

@@ -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 EdgelessThemeFilterProvider
extends Service
implements FilterProvider
{
constructor(private readonly docsService: DocsService) {
super();
}
filter$(params: FilterParams): Observable<Set<string>> {
const method = params.method as WorkspacePropertyFilter<'edgelessTheme'>;
if (method === 'is') {
return this.docsService.propertyValues$('edgelessColorTheme').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if ((value ?? 'system') === params.value) {
match.add(id);
}
}
return match;
})
);
} else if (method === 'is-not') {
return this.docsService.propertyValues$('edgelessColorTheme').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if ((value ?? 'system') !== params.value) {
match.add(id);
}
}
return match;
})
);
}
throw new Error(`Unsupported method: ${params.method}`);
}
}

View File

@@ -0,0 +1,45 @@
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';
export class IntegrationTypeFilterProvider
extends Service
implements FilterProvider
{
constructor(private readonly docsService: DocsService) {
super();
}
filter$(params: FilterParams): Observable<Set<string>> {
const method = params.method as 'is' | 'is-not';
if (method === 'is') {
return this.docsService.propertyValues$('integrationType').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if (value === params.value) {
match.add(id);
}
}
return match;
})
);
} else if (method === 'is-not') {
return this.docsService.propertyValues$('integrationType').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if (value !== params.value) {
match.add(id);
}
}
return match;
})
);
}
throw new Error(`Unsupported method: ${params.method}`);
}
}

View File

@@ -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 PageWidthFilterProvider extends Service implements FilterProvider {
constructor(private readonly docsService: DocsService) {
super();
}
filter$(params: FilterParams): Observable<Set<string>> {
const method = params.method as WorkspacePropertyFilter<'pageWidth'>;
if (method === 'is') {
return this.docsService.propertyValues$('pageWidth').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if ((value ?? 'standard') === params.value) {
match.add(id);
}
}
return match;
})
);
} else if (method === 'is-not') {
return this.docsService.propertyValues$('pageWidth').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if ((value ?? 'standard') !== params.value) {
match.add(id);
}
}
return match;
})
);
}
throw new Error(`Unsupported method: ${params.method}`);
}
}

View File

@@ -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 TemplateFilterProvider extends Service implements FilterProvider {
constructor(private readonly docsService: DocsService) {
super();
}
filter$(params: FilterParams): Observable<Set<string>> {
const method = params.method as WorkspacePropertyFilter<'template'>;
if (method === 'is') {
return this.docsService.propertyValues$('isTemplate').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if ((value ? 'true' : 'false') === params.value) {
match.add(id);
}
}
return match;
})
);
} else if (method === 'is-not') {
return this.docsService.propertyValues$('isTemplate').pipe(
map(values => {
const match = new Set<string>();
for (const [id, value] of values) {
if ((value ? 'true' : 'false') !== params.value) {
match.add(id);
}
}
return match;
})
);
}
throw new Error(`Unsupported method: ${params.method}`);
}
}

View File

@@ -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 EdgelessThemeGroupByProvider
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$('edgelessColorTheme').pipe(
map(values => {
const result = new Map<string, Set<string>>();
for (const [id, value] of values) {
const theme = value ?? 'system';
if (!result.has(theme)) {
result.set(theme, new Set([id]));
} else {
result.get(theme)?.add(id);
}
}
return result;
})
);
}
}

View File

@@ -0,0 +1,38 @@
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 IntegrationTypeGroupByProvider
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$('integrationType').pipe(
map(values => {
const result = new Map<string, Set<string>>();
for (const [id, value] of values) {
const integrationType = value;
if (!integrationType) {
continue;
}
if (!result.has(integrationType)) {
result.set(integrationType, new Set([id]));
} else {
result.get(integrationType)?.add(id);
}
}
return result;
})
);
}
}

View File

@@ -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 PageWidthGroupByProvider
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$('pageWidth').pipe(
map(values => {
const result = new Map<string, Set<string>>();
for (const [id, value] of values) {
const pageWidth = value ?? 'standard';
if (!result.has(pageWidth)) {
result.set(pageWidth, new Set([id]));
} else {
result.get(pageWidth)?.add(id);
}
}
return result;
})
);
}
}

View File

@@ -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 TemplateGroupByProvider
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$('isTemplate').pipe(
map(values => {
const result = new Map<string, Set<string>>();
for (const [id, value] of values) {
const isTemplate = value ? 'true' : 'false';
if (!result.has(isTemplate)) {
result.set(isTemplate, new Set([id]));
} else {
result.get(isTemplate)?.add(id);
}
}
return result;
})
);
}
}

View File

@@ -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 EdgelessThemeOrderByProvider
extends Service
implements OrderByProvider
{
constructor(private readonly docsService: DocsService) {
super();
}
orderBy$(
_items$: Observable<Set<string>>,
params: OrderByParams
): Observable<string[]> {
return this.docsService.propertyValues$('edgelessColorTheme').pipe(
map(values => {
const docs = Array.from(values).map(([id, value]) => ({
id,
theme: value ?? 'system',
}));
if (params.desc) {
return docs
.sort((a, b) => b.theme.localeCompare(a.theme))
.map(doc => doc.id);
} else {
return docs
.sort((a, b) => a.theme.localeCompare(b.theme))
.map(doc => doc.id);
}
})
);
}
}

View File

@@ -0,0 +1,41 @@
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 IntegrationTypeOrderByProvider
extends Service
implements OrderByProvider
{
constructor(private readonly docsService: DocsService) {
super();
}
orderBy$(
_items$: Observable<Set<string>>,
params: OrderByParams
): Observable<string[]> {
return this.docsService.propertyValues$('integrationType').pipe(
map(values => {
const docs = Array.from(values)
.filter(([, value]) => value !== undefined)
.map(([id, value]) => ({
id,
type: value as string,
}));
if (params.desc) {
return docs
.sort((a, b) => b.type.localeCompare(a.type))
.map(doc => doc.id);
} else {
return docs
.sort((a, b) => a.type.localeCompare(b.type))
.map(doc => doc.id);
}
})
);
}
}

View File

@@ -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 PageWidthOrderByProvider
extends Service
implements OrderByProvider
{
constructor(private readonly docsService: DocsService) {
super();
}
orderBy$(
_items$: Observable<Set<string>>,
params: OrderByParams
): Observable<string[]> {
return this.docsService.propertyValues$('pageWidth').pipe(
map(values => {
const docs = Array.from(values).map(([id, value]) => ({
id,
width: value ?? 'standard',
}));
if (params.desc) {
return docs
.sort((a, b) => b.width.localeCompare(a.width))
.map(doc => doc.id);
} else {
return docs
.sort((a, b) => a.width.localeCompare(b.width))
.map(doc => doc.id);
}
})
);
}
}

View File

@@ -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 TemplateOrderByProvider
extends Service
implements OrderByProvider
{
constructor(private readonly docsService: DocsService) {
super();
}
orderBy$(
_items$: Observable<Set<string>>,
params: OrderByParams
): Observable<string[]> {
return this.docsService.propertyValues$('isTemplate').pipe(
map(values => {
const docs = Array.from(values).map(([id, value]) => ({
id,
isTemplate: value ? 'true' : 'false',
}));
if (params.desc) {
return docs
.sort((a, b) => b.isTemplate.localeCompare(a.isTemplate))
.map(doc => doc.id);
} else {
return docs
.sort((a, b) => a.isTemplate.localeCompare(b.isTemplate))
.map(doc => doc.id);
}
})
);
}
}

View File

@@ -12,13 +12,17 @@ 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 { EdgelessThemeFilterProvider } from './impls/filters/edgeless-theme';
import { EmptyJournalFilterProvider } from './impls/filters/empty-journal';
import { FavoriteFilterProvider } from './impls/filters/favorite';
import { IntegrationTypeFilterProvider } from './impls/filters/integration-type';
import { JournalFilterProvider } from './impls/filters/journal';
import { PageWidthFilterProvider } from './impls/filters/page-width';
import { PropertyFilterProvider } from './impls/filters/property';
import { SharedFilterProvider } from './impls/filters/shared';
import { SystemFilterProvider } from './impls/filters/system';
import { TagsFilterProvider } from './impls/filters/tags';
import { TemplateFilterProvider } from './impls/filters/template';
import { TextPropertyFilterProvider } from './impls/filters/text';
import { TitleFilterProvider } from './impls/filters/title';
import { TrashFilterProvider } from './impls/filters/trash';
@@ -29,10 +33,14 @@ 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 { EdgelessThemeGroupByProvider } from './impls/group-by/edgeless-theme';
import { IntegrationTypeGroupByProvider } from './impls/group-by/integration-type';
import { JournalGroupByProvider } from './impls/group-by/journal';
import { PageWidthGroupByProvider } from './impls/group-by/page-width';
import { PropertyGroupByProvider } from './impls/group-by/property';
import { SystemGroupByProvider } from './impls/group-by/system';
import { TagsGroupByProvider } from './impls/group-by/tags';
import { TemplateGroupByProvider } from './impls/group-by/template';
import { TextPropertyGroupByProvider } from './impls/group-by/text';
import { UpdatedAtGroupByProvider } from './impls/group-by/updated-at';
import { UpdatedByGroupByProvider } from './impls/group-by/updated-by';
@@ -41,10 +49,14 @@ 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 { EdgelessThemeOrderByProvider } from './impls/order-by/edgeless-theme';
import { IntegrationTypeOrderByProvider } from './impls/order-by/integration-type';
import { JournalOrderByProvider } from './impls/order-by/journal';
import { PageWidthOrderByProvider } from './impls/order-by/page-width';
import { PropertyOrderByProvider } from './impls/order-by/property';
import { SystemOrderByProvider } from './impls/order-by/system';
import { TagsOrderByProvider } from './impls/order-by/tags';
import { TemplateOrderByProvider } from './impls/order-by/template';
import { TextPropertyOrderByProvider } from './impls/order-by/text';
import { UpdatedAtOrderByProvider } from './impls/order-by/updated-at';
import { UpdatedByOrderByProvider } from './impls/order-by/updated-by';
@@ -135,6 +147,31 @@ export function configureCollectionRulesModule(framework: Framework) {
.impl(FilterProvider('system:title'), TitleFilterProvider, [
DocsSearchService,
])
.impl(
FilterProvider('system:integrationType'),
IntegrationTypeFilterProvider,
[DocsService]
)
.impl(
FilterProvider('property:edgelessTheme'),
EdgelessThemeFilterProvider,
[DocsService]
)
.impl(FilterProvider('system:edgelessTheme'), EdgelessThemeFilterProvider, [
DocsService,
])
.impl(FilterProvider('property:template'), TemplateFilterProvider, [
DocsService,
])
.impl(FilterProvider('system:template'), TemplateFilterProvider, [
DocsService,
])
.impl(FilterProvider('property:pageWidth'), PageWidthFilterProvider, [
DocsService,
])
.impl(FilterProvider('system:pageWidth'), PageWidthFilterProvider, [
DocsService,
])
// --------------- Group By ---------------
.impl(GroupByProvider('system'), SystemGroupByProvider)
.impl(GroupByProvider('property'), PropertyGroupByProvider, [
@@ -199,6 +236,33 @@ export function configureCollectionRulesModule(framework: Framework) {
.impl(GroupByProvider('system:updatedBy'), UpdatedByGroupByProvider, [
DocsService,
])
.impl(
GroupByProvider('system:integrationType'),
IntegrationTypeGroupByProvider,
[DocsService]
)
.impl(
GroupByProvider('property:edgelessTheme'),
EdgelessThemeGroupByProvider,
[DocsService]
)
.impl(
GroupByProvider('system:edgelessTheme'),
EdgelessThemeGroupByProvider,
[DocsService]
)
.impl(GroupByProvider('property:pageWidth'), PageWidthGroupByProvider, [
DocsService,
])
.impl(GroupByProvider('system:pageWidth'), PageWidthGroupByProvider, [
DocsService,
])
.impl(GroupByProvider('property:template'), TemplateGroupByProvider, [
DocsService,
])
.impl(GroupByProvider('system:template'), TemplateGroupByProvider, [
DocsService,
])
// --------------- Order By ---------------
.impl(OrderByProvider('system'), SystemOrderByProvider)
.impl(OrderByProvider('property'), PropertyOrderByProvider, [
@@ -262,5 +326,32 @@ export function configureCollectionRulesModule(framework: Framework) {
])
.impl(OrderByProvider('system:updatedBy'), UpdatedByOrderByProvider, [
DocsService,
])
.impl(
OrderByProvider('system:integrationType'),
IntegrationTypeOrderByProvider,
[DocsService]
)
.impl(
OrderByProvider('property:edgelessTheme'),
EdgelessThemeOrderByProvider,
[DocsService]
)
.impl(
OrderByProvider('system:edgelessTheme'),
EdgelessThemeOrderByProvider,
[DocsService]
)
.impl(OrderByProvider('property:pageWidth'), PageWidthOrderByProvider, [
DocsService,
])
.impl(OrderByProvider('system:pageWidth'), PageWidthOrderByProvider, [
DocsService,
])
.impl(OrderByProvider('property:template'), TemplateOrderByProvider, [
DocsService,
])
.impl(OrderByProvider('system:template'), TemplateOrderByProvider, [
DocsService,
]);
}

View File

@@ -39,9 +39,9 @@ export type WorkspacePropertyTypes = {
createdAt: { filter: DateFilters };
docPrimaryMode: { filter: 'is' | 'is-not' };
journal: { filter: 'is' | 'is-not' };
edgelessTheme: { filter: never };
pageWidth: { filter: never };
template: { filter: never };
edgelessTheme: { filter: 'is' | 'is-not' };
pageWidth: { filter: 'is' | 'is-not' };
template: { filter: 'is' | 'is-not' };
unknown: { filter: never };
};
export type WorkspacePropertyType = keyof WorkspacePropertyTypes;