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
@@ -0,0 +1,5 @@
export {
EdgelessThemeDocListProperty,
EdgelessThemeFilterValue,
EdgelessThemeGroupHeader,
} from '../workspace-property-types/edgeless-theme';
@@ -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>>;
@@ -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>
);
};
@@ -0,0 +1,5 @@
export {
PageWidthDocListProperty,
PageWidthFilterValue,
PageWidthGroupHeader,
} from '../workspace-property-types/page-width';
@@ -0,0 +1,5 @@
export {
TemplateDocListProperty,
TemplateFilterValue,
TemplateGroupHeader,
} from '../workspace-property-types/template';
@@ -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}
@@ -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>
);
};
@@ -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,
@@ -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}
@@ -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>
);
};
@@ -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>
);
};