diff --git a/packages/frontend/component/src/components/page-list/filter/filter-tag-translation.tsx b/packages/frontend/component/src/components/page-list/filter/filter-tag-translation.tsx
index 587ca00d75..1c3d8c27b6 100644
--- a/packages/frontend/component/src/components/page-list/filter/filter-tag-translation.tsx
+++ b/packages/frontend/component/src/components/page-list/filter/filter-tag-translation.tsx
@@ -19,6 +19,8 @@ const useFilterTag = ({ name }: FilterTagProps) => {
return t['com.affine.filter.after']();
case 'before':
return t['com.affine.filter.before']();
+ case 'last':
+ return t['com.affine.filter.last']();
case 'is':
return t['com.affine.filter.is']();
case 'is not empty':
diff --git a/packages/frontend/component/src/components/page-list/filter/literal-matcher.tsx b/packages/frontend/component/src/components/page-list/filter/literal-matcher.tsx
index 132fed81d5..6a4a77e1dc 100644
--- a/packages/frontend/component/src/components/page-list/filter/literal-matcher.tsx
+++ b/packages/frontend/component/src/components/page-list/filter/literal-matcher.tsx
@@ -1,11 +1,13 @@
import type { LiteralValue, Tag } from '@affine/env/filter';
import dayjs from 'dayjs';
-import type { ReactNode } from 'react';
+import { type ReactNode } from 'react';
+import Input from '../../../ui/input';
+import { Menu, MenuItem } from '../../../ui/menu';
import { AFFiNEDatePicker } from '../../date-picker';
import { FilterTag } from './filter-tag-translation';
import { inputStyle } from './index.css';
-import { tBoolean, tDate, tTag } from './logical/custom-type';
+import { tBoolean, tDate, tDateRange, tTag } from './logical/custom-type';
import { Matcher } from './logical/matcher';
import type { TType } from './logical/typesystem';
import { tArray, typesystem } from './logical/typesystem';
@@ -21,6 +23,37 @@ export const literalMatcher = new Matcher<{
return typesystem.isSubtype(type, target);
});
+literalMatcher.register(tDateRange.create(), {
+ render: ({ value, onChange }) => (
+
+ ),
+});
+
literalMatcher.register(tBoolean.create(), {
render: ({ value, onChange }) => (
({
name: 'Tag',
supers: [],
});
+
+export const tDateRange = typesystem.defineData(
+ DataHelper.create<{ value: number }>('DateRange')
+);
diff --git a/packages/frontend/component/src/components/page-list/filter/vars.tsx b/packages/frontend/component/src/components/page-list/filter/vars.tsx
index 8bb53ab57d..a1b36cf8b0 100644
--- a/packages/frontend/component/src/components/page-list/filter/vars.tsx
+++ b/packages/frontend/component/src/components/page-list/filter/vars.tsx
@@ -11,7 +11,7 @@ import type { ReactNode } from 'react';
import { MenuIcon, MenuItem, MenuSeparator } from '../../../ui/menu';
import { FilterTag } from './filter-tag-translation';
import * as styles from './index.css';
-import { tBoolean, tDate, tTag } from './logical/custom-type';
+import { tBoolean, tDate, tDateRange, tTag } from './logical/custom-type';
import { Matcher } from './logical/matcher';
import type { TFunction } from './logical/typesystem';
import {
@@ -161,6 +161,24 @@ filterMatcher.register(
}
);
+filterMatcher.register(
+ tFunction({
+ args: [tDate.create(), tDateRange.create()],
+ rt: tBoolean.create(),
+ }),
+ {
+ name: 'last',
+ defaultArgs: () => [30], // Default to the last 30 days
+ impl: (date, n) => {
+ if (typeof date !== 'number' || typeof n !== 'number') {
+ throw new Error('Argument type error: date and n must be numbers');
+ }
+ const startDate = dayjs().subtract(n, 'day').startOf('day').valueOf();
+ return date > startDate;
+ },
+ }
+);
+
filterMatcher.register(
tFunction({
args: [tDate.create(), tDate.create()],
diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json
index 32aa077c25..4aa27db82a 100644
--- a/packages/frontend/i18n/src/resources/en.json
+++ b/packages/frontend/i18n/src/resources/en.json
@@ -608,6 +608,7 @@
"com.affine.filter": "Filter",
"com.affine.filter.after": "after",
"com.affine.filter.before": "before",
+ "com.affine.filter.last": "last",
"com.affine.filter.contains all": "contains all",
"com.affine.filter.contains one of": "contains one of",
"com.affine.filter.does not contains all": "does not contains all",
diff --git a/packages/frontend/i18n/src/resources/zh-Hans.json b/packages/frontend/i18n/src/resources/zh-Hans.json
index 47833ee09c..54a6b9c7a7 100644
--- a/packages/frontend/i18n/src/resources/zh-Hans.json
+++ b/packages/frontend/i18n/src/resources/zh-Hans.json
@@ -593,6 +593,7 @@
"com.affine.filter": "筛选",
"com.affine.filter.after": "晚于",
"com.affine.filter.before": "早于",
+ "com.affine.filter.last": "最近",
"com.affine.filter.contains all": "包含以下所有",
"com.affine.filter.contains one of": "包含以下之一",
"com.affine.filter.does not contains all": "不包含以下所有",