feat(core): add number property filter and group by (#12483)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced filtering options for number properties with multiple comparison operators and empty/not empty checks. - Added support for grouping and ordering by number properties. - Introduced new UI components for number property filtering and group headers. - Improved draft completion behavior for text and tags filters using empty/not empty methods. - **Bug Fixes** - Improved consistency in draft completion behavior when filtering by text or tags using empty/not empty methods. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -70,7 +70,12 @@ import {
|
|||||||
JournalGroupHeader,
|
JournalGroupHeader,
|
||||||
JournalValue,
|
JournalValue,
|
||||||
} from './journal';
|
} from './journal';
|
||||||
import { NumberDocListProperty, NumberValue } from './number';
|
import {
|
||||||
|
NumberDocListProperty,
|
||||||
|
NumberFilterValue,
|
||||||
|
NumberGroupHeader,
|
||||||
|
NumberValue,
|
||||||
|
} from './number';
|
||||||
import {
|
import {
|
||||||
PageWidthDocListProperty,
|
PageWidthDocListProperty,
|
||||||
PageWidthFilterValue,
|
PageWidthFilterValue,
|
||||||
@@ -158,8 +163,23 @@ export const WorkspacePropertyTypes = {
|
|||||||
value: NumberValue,
|
value: NumberValue,
|
||||||
name: 'com.affine.page-properties.property.number',
|
name: 'com.affine.page-properties.property.number',
|
||||||
description: 'com.affine.page-properties.property.number.tooltips',
|
description: 'com.affine.page-properties.property.number.tooltips',
|
||||||
|
filterMethod: {
|
||||||
|
'<': '<',
|
||||||
|
'=': '=',
|
||||||
|
'≠': '≠',
|
||||||
|
'≥': '≥',
|
||||||
|
'≤': '≤',
|
||||||
|
'>': '>',
|
||||||
|
'is-not-empty': 'com.affine.filter.is not empty',
|
||||||
|
'is-empty': 'com.affine.filter.is empty',
|
||||||
|
},
|
||||||
|
allowInGroupBy: true,
|
||||||
|
allowInOrderBy: true,
|
||||||
|
filterValue: NumberFilterValue,
|
||||||
|
defaultFilter: { method: 'is-not-empty' },
|
||||||
showInDocList: 'stack',
|
showInDocList: 'stack',
|
||||||
docListProperty: NumberDocListProperty,
|
docListProperty: NumberDocListProperty,
|
||||||
|
groupHeader: NumberGroupHeader,
|
||||||
},
|
},
|
||||||
checkbox: {
|
checkbox: {
|
||||||
icon: CheckBoxCheckLinearIcon,
|
icon: CheckBoxCheckLinearIcon,
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { PropertyValue } from '@affine/component';
|
import { Input, Menu, type MenuRef, PropertyValue } from '@affine/component';
|
||||||
|
import type { FilterParams } from '@affine/core/modules/collection-rules';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { NumberIcon } from '@blocksuite/icons/rc';
|
import { NumberIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import {
|
import {
|
||||||
type ChangeEventHandler,
|
type ChangeEventHandler,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
|
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
|
||||||
import { StackProperty } from '../explorer/docs-view/stack-property';
|
import { StackProperty } from '../explorer/docs-view/stack-property';
|
||||||
|
import type { GroupHeaderProps } from '../explorer/types';
|
||||||
import type { PropertyValueProps } from '../properties/types';
|
import type { PropertyValueProps } from '../properties/types';
|
||||||
import * as styles from './number.css';
|
import * as styles from './number.css';
|
||||||
|
|
||||||
@@ -58,6 +64,109 @@ export const NumberValue = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NumberFilterValue = ({
|
||||||
|
filter,
|
||||||
|
isDraft,
|
||||||
|
onDraftCompleted,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
filter: FilterParams;
|
||||||
|
isDraft?: boolean;
|
||||||
|
onDraftCompleted?: () => void;
|
||||||
|
onChange?: (filter: FilterParams) => void;
|
||||||
|
}) => {
|
||||||
|
const [tempValue, setTempValue] = useState(filter.value || '');
|
||||||
|
const [valueMenuOpen, setValueMenuOpen] = useState(false);
|
||||||
|
const menuRef = useRef<MenuRef>(null);
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDraft) {
|
||||||
|
menuRef.current?.changeOpen(true);
|
||||||
|
}
|
||||||
|
}, [isDraft]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// update temp value with new filter value
|
||||||
|
setTempValue(filter.value || '');
|
||||||
|
}, [filter.value]);
|
||||||
|
|
||||||
|
const submitTempValue = useCallback(() => {
|
||||||
|
if (tempValue !== (filter.value || '')) {
|
||||||
|
onChange?.({
|
||||||
|
...filter,
|
||||||
|
value: tempValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [filter, onChange, tempValue]);
|
||||||
|
|
||||||
|
const handleInputKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key !== 'Escape') return;
|
||||||
|
submitTempValue();
|
||||||
|
setValueMenuOpen(false);
|
||||||
|
onDraftCompleted?.();
|
||||||
|
},
|
||||||
|
[submitTempValue, onDraftCompleted]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleInputEnter = useCallback(() => {
|
||||||
|
submitTempValue();
|
||||||
|
setValueMenuOpen(false);
|
||||||
|
onDraftCompleted?.();
|
||||||
|
}, [submitTempValue, onDraftCompleted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isDraft &&
|
||||||
|
(filter.method === 'is-not-empty' || filter.method === 'is-empty')
|
||||||
|
) {
|
||||||
|
onDraftCompleted?.();
|
||||||
|
}
|
||||||
|
}, [isDraft, filter.method, onDraftCompleted]);
|
||||||
|
|
||||||
|
return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? (
|
||||||
|
<Menu
|
||||||
|
ref={menuRef}
|
||||||
|
rootOptions={{
|
||||||
|
open: valueMenuOpen,
|
||||||
|
onOpenChange: setValueMenuOpen,
|
||||||
|
onClose: onDraftCompleted,
|
||||||
|
}}
|
||||||
|
contentOptions={{
|
||||||
|
onPointerDownOutside: submitTempValue,
|
||||||
|
sideOffset: -28,
|
||||||
|
}}
|
||||||
|
items={
|
||||||
|
<Input
|
||||||
|
inputStyle={{
|
||||||
|
fontSize: cssVar('fontBase'),
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
inputMode="decimal"
|
||||||
|
autoFocus
|
||||||
|
autoSelect
|
||||||
|
value={tempValue}
|
||||||
|
onChange={value => {
|
||||||
|
setTempValue(value);
|
||||||
|
}}
|
||||||
|
onEnter={handleInputEnter}
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
style={{ height: 34, borderRadius: 4 }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{filter.value ? (
|
||||||
|
<span>{filter.value}</span>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: cssVarV2('text/placeholder') }}>
|
||||||
|
{t['com.affine.filter.empty']()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
export const NumberDocListProperty = ({ value }: { value: number }) => {
|
export const NumberDocListProperty = ({ value }: { value: number }) => {
|
||||||
if (value !== 0 && !value) {
|
if (value !== 0 && !value) {
|
||||||
return null;
|
return null;
|
||||||
@@ -65,3 +174,13 @@ export const NumberDocListProperty = ({ value }: { value: number }) => {
|
|||||||
|
|
||||||
return <StackProperty icon={<NumberIcon />}>{value}</StackProperty>;
|
return <StackProperty icon={<NumberIcon />}>{value}</StackProperty>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NumberGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const number = groupId || t['com.affine.filter.empty']();
|
||||||
|
return (
|
||||||
|
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
|
||||||
|
{number}
|
||||||
|
</PlainTextDocGroupHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -98,6 +98,16 @@ export const TagsFilterValue = ({
|
|||||||
},
|
},
|
||||||
[filter, onChange, selectedTags]
|
[filter, onChange, selectedTags]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isDraft &&
|
||||||
|
(filter.method === 'is-not-empty' || filter.method === 'is-empty')
|
||||||
|
) {
|
||||||
|
onDraftCompleted?.();
|
||||||
|
}
|
||||||
|
}, [isDraft, filter.method, onDraftCompleted]);
|
||||||
|
|
||||||
return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? (
|
return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? (
|
||||||
<WorkspaceTagsInlineEditor
|
<WorkspaceTagsInlineEditor
|
||||||
placeholder={
|
placeholder={
|
||||||
|
|||||||
@@ -216,14 +216,25 @@ export const TextFilterValue = ({
|
|||||||
if (e.key !== 'Escape') return;
|
if (e.key !== 'Escape') return;
|
||||||
submitTempValue();
|
submitTempValue();
|
||||||
setValueMenuOpen(false);
|
setValueMenuOpen(false);
|
||||||
|
onDraftCompleted?.();
|
||||||
},
|
},
|
||||||
[submitTempValue]
|
[submitTempValue, onDraftCompleted]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleInputEnter = useCallback(() => {
|
const handleInputEnter = useCallback(() => {
|
||||||
submitTempValue();
|
submitTempValue();
|
||||||
setValueMenuOpen(false);
|
setValueMenuOpen(false);
|
||||||
}, [submitTempValue]);
|
onDraftCompleted?.();
|
||||||
|
}, [submitTempValue, onDraftCompleted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isDraft &&
|
||||||
|
(filter.method === 'is-not-empty' || filter.method === 'is-empty')
|
||||||
|
) {
|
||||||
|
onDraftCompleted?.();
|
||||||
|
}
|
||||||
|
}, [isDraft, filter.method, onDraftCompleted]);
|
||||||
|
|
||||||
return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? (
|
return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? (
|
||||||
<Menu
|
<Menu
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
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 NumberPropertyFilterProvider
|
||||||
|
extends Service
|
||||||
|
implements FilterProvider
|
||||||
|
{
|
||||||
|
constructor(private readonly docsService: DocsService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
filter$(params: FilterParams): Observable<Set<string>> {
|
||||||
|
const method = params.method as WorkspacePropertyFilter<'number'>;
|
||||||
|
const values$ = this.docsService.propertyValues$('custom:' + params.key);
|
||||||
|
const filterValue = Number(params.value);
|
||||||
|
if (method === 'is-not-empty') {
|
||||||
|
return values$.pipe(
|
||||||
|
map(o => {
|
||||||
|
const match = new Set<string>();
|
||||||
|
for (const [id, value] of o) {
|
||||||
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (method === 'is-empty') {
|
||||||
|
return values$.pipe(
|
||||||
|
map(o => {
|
||||||
|
const match = new Set<string>();
|
||||||
|
for (const [id, value] of o) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
method === '=' ||
|
||||||
|
method === '≠' ||
|
||||||
|
method === '>' ||
|
||||||
|
method === '<' ||
|
||||||
|
method === '≥' ||
|
||||||
|
method === '≤'
|
||||||
|
) {
|
||||||
|
return values$.pipe(
|
||||||
|
map(o => {
|
||||||
|
const match = new Set<string>();
|
||||||
|
for (const [id, value] of o) {
|
||||||
|
const numValue = Number(value);
|
||||||
|
switch (method) {
|
||||||
|
case '=':
|
||||||
|
if (Math.abs(numValue - filterValue) < Number.EPSILON) {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '≠':
|
||||||
|
if (Math.abs(numValue - filterValue) >= Number.EPSILON) {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
if (numValue > filterValue) {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
if (numValue < filterValue) {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '≥':
|
||||||
|
if (numValue >= filterValue) {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '≤':
|
||||||
|
if (numValue <= filterValue) {
|
||||||
|
match.add(id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error(`Unsupported method: ${method}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 NumberPropertyGroupByProvider
|
||||||
|
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) {
|
||||||
|
const number = Number(value);
|
||||||
|
if (Number.isNaN(number)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// normalize all number to string
|
||||||
|
const strValue = String(number);
|
||||||
|
const set = result.get(strValue) ?? new Set<string>();
|
||||||
|
set.add(id);
|
||||||
|
result.set(strValue, set);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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 NumberPropertyOrderByProvider
|
||||||
|
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)
|
||||||
|
.map(v => [v[0], Number(v[1])])
|
||||||
|
.filter((i): i is [string, number] => !Number.isNaN(i[1])) // filter NaN value
|
||||||
|
.sort((a, b) => (a[1] - b[1]) * (isDesc ? -1 : 1))
|
||||||
|
.map(i => i[0]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import { EmptyJournalFilterProvider } from './impls/filters/empty-journal';
|
|||||||
import { FavoriteFilterProvider } from './impls/filters/favorite';
|
import { FavoriteFilterProvider } from './impls/filters/favorite';
|
||||||
import { IntegrationTypeFilterProvider } from './impls/filters/integration-type';
|
import { IntegrationTypeFilterProvider } from './impls/filters/integration-type';
|
||||||
import { JournalFilterProvider } from './impls/filters/journal';
|
import { JournalFilterProvider } from './impls/filters/journal';
|
||||||
|
import { NumberPropertyFilterProvider } from './impls/filters/number';
|
||||||
import { PageWidthFilterProvider } from './impls/filters/page-width';
|
import { PageWidthFilterProvider } from './impls/filters/page-width';
|
||||||
import { PropertyFilterProvider } from './impls/filters/property';
|
import { PropertyFilterProvider } from './impls/filters/property';
|
||||||
import { SharedFilterProvider } from './impls/filters/shared';
|
import { SharedFilterProvider } from './impls/filters/shared';
|
||||||
@@ -36,6 +37,7 @@ import { DocPrimaryModeGroupByProvider } from './impls/group-by/doc-primary-mode
|
|||||||
import { EdgelessThemeGroupByProvider } from './impls/group-by/edgeless-theme';
|
import { EdgelessThemeGroupByProvider } from './impls/group-by/edgeless-theme';
|
||||||
import { IntegrationTypeGroupByProvider } from './impls/group-by/integration-type';
|
import { IntegrationTypeGroupByProvider } from './impls/group-by/integration-type';
|
||||||
import { JournalGroupByProvider } from './impls/group-by/journal';
|
import { JournalGroupByProvider } from './impls/group-by/journal';
|
||||||
|
import { NumberPropertyGroupByProvider } from './impls/group-by/number';
|
||||||
import { PageWidthGroupByProvider } from './impls/group-by/page-width';
|
import { PageWidthGroupByProvider } from './impls/group-by/page-width';
|
||||||
import { PropertyGroupByProvider } from './impls/group-by/property';
|
import { PropertyGroupByProvider } from './impls/group-by/property';
|
||||||
import { SystemGroupByProvider } from './impls/group-by/system';
|
import { SystemGroupByProvider } from './impls/group-by/system';
|
||||||
@@ -52,6 +54,7 @@ import { DocPrimaryModeOrderByProvider } from './impls/order-by/doc-primary-mode
|
|||||||
import { EdgelessThemeOrderByProvider } from './impls/order-by/edgeless-theme';
|
import { EdgelessThemeOrderByProvider } from './impls/order-by/edgeless-theme';
|
||||||
import { IntegrationTypeOrderByProvider } from './impls/order-by/integration-type';
|
import { IntegrationTypeOrderByProvider } from './impls/order-by/integration-type';
|
||||||
import { JournalOrderByProvider } from './impls/order-by/journal';
|
import { JournalOrderByProvider } from './impls/order-by/journal';
|
||||||
|
import { NumberPropertyOrderByProvider } from './impls/order-by/number';
|
||||||
import { PageWidthOrderByProvider } from './impls/order-by/page-width';
|
import { PageWidthOrderByProvider } from './impls/order-by/page-width';
|
||||||
import { PropertyOrderByProvider } from './impls/order-by/property';
|
import { PropertyOrderByProvider } from './impls/order-by/property';
|
||||||
import { SystemOrderByProvider } from './impls/order-by/system';
|
import { SystemOrderByProvider } from './impls/order-by/system';
|
||||||
@@ -81,6 +84,9 @@ export function configureCollectionRulesModule(framework: Framework) {
|
|||||||
.impl(FilterProvider('property:text'), TextPropertyFilterProvider, [
|
.impl(FilterProvider('property:text'), TextPropertyFilterProvider, [
|
||||||
DocsService,
|
DocsService,
|
||||||
])
|
])
|
||||||
|
.impl(FilterProvider('property:number'), NumberPropertyFilterProvider, [
|
||||||
|
DocsService,
|
||||||
|
])
|
||||||
.impl(FilterProvider('property:tags'), TagsFilterProvider, [
|
.impl(FilterProvider('property:tags'), TagsFilterProvider, [
|
||||||
TagService,
|
TagService,
|
||||||
DocsService,
|
DocsService,
|
||||||
@@ -196,6 +202,9 @@ export function configureCollectionRulesModule(framework: Framework) {
|
|||||||
.impl(GroupByProvider('property:text'), TextPropertyGroupByProvider, [
|
.impl(GroupByProvider('property:text'), TextPropertyGroupByProvider, [
|
||||||
DocsService,
|
DocsService,
|
||||||
])
|
])
|
||||||
|
.impl(GroupByProvider('property:number'), NumberPropertyGroupByProvider, [
|
||||||
|
DocsService,
|
||||||
|
])
|
||||||
.impl(
|
.impl(
|
||||||
GroupByProvider('property:docPrimaryMode'),
|
GroupByProvider('property:docPrimaryMode'),
|
||||||
DocPrimaryModeGroupByProvider,
|
DocPrimaryModeGroupByProvider,
|
||||||
@@ -293,6 +302,9 @@ export function configureCollectionRulesModule(framework: Framework) {
|
|||||||
.impl(OrderByProvider('property:text'), TextPropertyOrderByProvider, [
|
.impl(OrderByProvider('property:text'), TextPropertyOrderByProvider, [
|
||||||
DocsService,
|
DocsService,
|
||||||
])
|
])
|
||||||
|
.impl(OrderByProvider('property:number'), NumberPropertyOrderByProvider, [
|
||||||
|
DocsService,
|
||||||
|
])
|
||||||
.impl(OrderByProvider('property:date'), DatePropertyOrderByProvider, [
|
.impl(OrderByProvider('property:date'), DatePropertyOrderByProvider, [
|
||||||
DocsService,
|
DocsService,
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export type WorkspacePropertyTypes = {
|
|||||||
filter: 'is' | 'is-not' | 'is-not-empty' | 'is-empty';
|
filter: 'is' | 'is-not' | 'is-not-empty' | 'is-empty';
|
||||||
};
|
};
|
||||||
number: {
|
number: {
|
||||||
filter: 'is' | 'is-not' | 'is-not-empty' | 'is-empty';
|
filter: '=' | '≠' | '>' | '<' | '≥' | '≤' | 'is-not-empty' | 'is-empty';
|
||||||
};
|
};
|
||||||
checkbox: {
|
checkbox: {
|
||||||
filter: 'is' | 'is-not';
|
filter: 'is' | 'is-not';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"de": 94,
|
"de": 94,
|
||||||
"el-GR": 94,
|
"el-GR": 94,
|
||||||
"en": 100,
|
"en": 100,
|
||||||
"es-AR": 95,
|
"es-AR": 94,
|
||||||
"es-CL": 96,
|
"es-CL": 96,
|
||||||
"es": 94,
|
"es": 94,
|
||||||
"fa": 94,
|
"fa": 94,
|
||||||
|
|||||||
Reference in New Issue
Block a user