diff --git a/packages/component/src/components/page-list/all-page.tsx b/packages/component/src/components/page-list/all-page.tsx index 1594b8d460..2459985ae8 100644 --- a/packages/component/src/components/page-list/all-page.tsx +++ b/packages/component/src/components/page-list/all-page.tsx @@ -5,6 +5,7 @@ import { TableHead, TableRow, } from '@affine/component'; +import { DEFAULT_SORT_KEY } from "@affine/env/constant"; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ArrowDownBigIcon, ArrowUpBigIcon } from '@blocksuite/icons'; import { useMediaQuery, useTheme } from '@mui/material'; @@ -111,7 +112,7 @@ export const PageList = ({ }: PageListProps) => { const sorter = useSorter({ data: list, - key: 'updatedDate', + key: DEFAULT_SORT_KEY, order: 'desc', }); @@ -128,6 +129,14 @@ export const PageList = ({ ); } + const groupKey = + sorter.key === 'createDate' || sorter.key === 'updatedDate' + ? sorter.key + : // default sort + !sorter.key + ? DEFAULT_SORT_KEY + : undefined; + return ( @@ -139,6 +148,7 @@ export const PageList = ({ />
diff --git a/packages/component/src/components/page-list/all-pages-body.tsx b/packages/component/src/components/page-list/all-pages-body.tsx index 55a57b6a32..2b0c42b7de 100644 --- a/packages/component/src/components/page-list/all-pages-body.tsx +++ b/packages/component/src/components/page-list/all-pages-body.tsx @@ -1,29 +1,35 @@ import { TableBody, TableCell } from '@affine/component'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useMediaQuery, useTheme } from '@mui/material'; +import { Fragment } from 'react'; import { FavoriteTag } from './components/favorite-tag'; import { TitleCell } from './components/title-cell'; import { OperationCell } from './operation-cell'; import { StyledTableRow } from './styles'; -import type { ListData } from './type'; +import type { DateKey, ListData } from './type'; +import { useDateGroup } from './use-date-group'; import { formatDate } from './utils'; export const AllPagesBody = ({ isPublicWorkspace, data, + groupKey, }: { isPublicWorkspace: boolean; data: ListData[]; + groupKey?: DateKey; }) => { const t = useAFFiNEI18N(); const theme = useTheme(); const isSmallDevices = useMediaQuery(theme.breakpoints.down('sm')); + const dataWithGroup = useDateGroup({ data, key: groupKey }); return ( - {data.map( + {dataWithGroup.map( ( { + groupName, pageId, title, icon, @@ -40,60 +46,74 @@ export const AllPagesBody = ({ index ) => { return ( - - - - - {!isPublicWorkspace && ( + + {groupName && + (index === 0 || + dataWithGroup[index - 1].groupName !== groupName) && ( + + + {groupName} + + + )} + + - )} - + + {!isPublicWorkspace && ( + + + + + )} + + ); } )} diff --git a/packages/component/src/components/page-list/mobile.tsx b/packages/component/src/components/page-list/mobile.tsx index 20146b861a..2c738219d2 100644 --- a/packages/component/src/components/page-list/mobile.tsx +++ b/packages/component/src/components/page-list/mobile.tsx @@ -71,7 +71,12 @@ export const AllPageListMobileView = ({ createNewPage={createNewPage} createNewEdgeless={createNewEdgeless} /> - +
); diff --git a/packages/component/src/components/page-list/type.ts b/packages/component/src/components/page-list/type.ts index fcec3b1854..70e498aa37 100644 --- a/packages/component/src/components/page-list/type.ts +++ b/packages/component/src/components/page-list/type.ts @@ -1,3 +1,12 @@ +/** + * Get the keys of an object type whose values are of a given type + * + * See https://stackoverflow.com/questions/54520676/in-typescript-how-to-get-the-keys-of-an-object-type-whose-values-are-of-a-given + */ +export type KeysMatching = { + [K in keyof T]-?: T[K] extends V ? K : never; +}[keyof T]; + export type ListData = { pageId: string; icon: JSX.Element; @@ -13,6 +22,8 @@ export type ListData = { onDisablePublicSharing: () => void; }; +export type DateKey = KeysMatching; + export type TrashListData = { pageId: string; icon: JSX.Element; diff --git a/packages/component/src/components/page-list/use-date-group.tsx b/packages/component/src/components/page-list/use-date-group.tsx new file mode 100644 index 0000000000..65101f1e34 --- /dev/null +++ b/packages/component/src/components/page-list/use-date-group.tsx @@ -0,0 +1,65 @@ +import { useAFFiNEI18N } from '@affine/i18n/hooks'; + +import type { DateKey, ListData } from './type'; +import { + isLastMonth, + isLastWeek, + isLastYear, + isToday, + isYesterday, +} from './utils'; + +export const useDateGroup = ({ + data, + key, +}: { + data: ListData[]; + key?: DateKey; +}) => { + const t = useAFFiNEI18N(); + if (!key) { + return data.map(item => ({ ...item, groupName: '' })); + } + + const fallbackGroup = { + id: 'earlier', + label: t['com.affine.earlier'](), + match: (_date: Date) => true, + }; + + const groups = [ + { + id: 'today', + label: t['com.affine.today'](), + match: (date: Date) => isToday(date), + }, + { + id: 'yesterday', + label: t['com.affine.yesterday'](), + match: (date: Date) => isYesterday(date) && !isToday(date), + }, + { + id: 'lastWeek', + label: t['com.affine.lastWeek'](), + match: (date: Date) => isLastWeek(date) && !isYesterday(date), + }, + { + id: 'lastMonth', + label: t['com.affine.lastMonth'](), + match: (date: Date) => isLastMonth(date) && !isLastWeek(date), + }, + { + id: 'lastYear', + label: t['com.affine.lastYear'](), + match: (date: Date) => isLastYear(date) && !isLastMonth(date), + }, + ] as const; + + return data.map(item => { + const group = groups.find(group => group.match(item[key])) ?? fallbackGroup; + return { + ...item, + groupName: group.label, + }; + }); +}; diff --git a/packages/component/src/components/page-list/utils.tsx b/packages/component/src/components/page-list/utils.tsx index fa0c593e29..e464eac35f 100644 --- a/packages/component/src/components/page-list/utils.tsx +++ b/packages/component/src/components/page-list/utils.tsx @@ -1,11 +1,51 @@ -const isToday = (date: Date) => { +export function isToday(date: Date): boolean { const today = new Date(); return ( date.getDate() == today.getDate() && date.getMonth() == today.getMonth() && date.getFullYear() == today.getFullYear() ); -}; +} + +export function isYesterday(date: Date): boolean { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + return ( + date.getFullYear() === yesterday.getFullYear() && + date.getMonth() === yesterday.getMonth() && + date.getDate() === yesterday.getDate() + ); +} + +export function isLastWeek(date: Date): boolean { + const today = new Date(); + const lastWeek = new Date( + today.getFullYear(), + today.getMonth(), + today.getDate() - 7 + ); + return date >= lastWeek && date < today; +} + +export function isLastMonth(date: Date): boolean { + const today = new Date(); + const lastMonth = new Date( + today.getFullYear(), + today.getMonth() - 1, + today.getDate() + ); + return date >= lastMonth && date < today; +} + +export function isLastYear(date: Date): boolean { + const today = new Date(); + const lastYear = new Date( + today.getFullYear() - 1, + today.getMonth(), + today.getDate() + ); + return date >= lastYear && date < today; +} export const formatDate = (date: Date): string => { // yyyy-mm-dd MM-DD HH:mm diff --git a/packages/component/src/stories/page-list.stories.tsx b/packages/component/src/stories/page-list.stories.tsx index 83adf78d05..34bb93fa9b 100644 --- a/packages/component/src/stories/page-list.stories.tsx +++ b/packages/component/src/stories/page-list.stories.tsx @@ -79,7 +79,7 @@ AffineAllPageList.args = { removeToTrash: () => toast('Remove to trash'), }, { - pageId: '1', + pageId: '3', favorite: false, icon: , isPublicPage: true, diff --git a/packages/env/src/constant.ts b/packages/env/src/constant.ts index 9a7128f52b..31096cb938 100644 --- a/packages/env/src/constant.ts +++ b/packages/env/src/constant.ts @@ -5,6 +5,7 @@ export const DEFAULT_WORKSPACE_NAME = 'Demo Workspace'; export const UNTITLED_WORKSPACE_NAME = 'Untitled'; export const DEFAULT_HELLO_WORLD_PAGE_ID = 'hello-world'; +export const DEFAULT_SORT_KEY = 'updatedDate'; export const enum MessageCode { loginError, noPermission, diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index 7576dae42a..b71802ce1e 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -279,6 +279,12 @@ "com.affine.updater.update-available": "Update available", "com.affine.updater.open-download-page": "Open download page", "com.affine.updater.restart-to-update": "Restart to install update", + "com.affine.today": "Today", + "com.affine.yesterday": "Yesterday", + "com.affine.lastWeek": "Last week", + "com.affine.lastMonth": "Last month", + "com.affine.lastYear": "Last year", + "com.affine.earlier": "Earlier", "com.affine.workspace.cannot-delete": "You cannot delete the last workspace", "FILE_ALREADY_EXISTS": "File already exists", "others": "Others", diff --git a/tests/parallels/affine/affine-public-workspace.spec.ts b/tests/parallels/affine/affine-public-workspace.spec.ts index 0b8920ae82..4403eb1b6a 100644 --- a/tests/parallels/affine/affine-public-workspace.spec.ts +++ b/tests/parallels/affine/affine-public-workspace.spec.ts @@ -62,7 +62,7 @@ test('access public workspace page', async ({ page, browser }) => { timeout: 10000, }); await clickSideBarAllPageButton(page); - await page.locator('tr').nth(1).click(); + await page.locator('tr').nth(2).click(); const url = page.url(); const context = await browser.newContext(); const page2 = await context.newPage(); diff --git a/tests/parallels/debug-page-broadcast.spec.ts b/tests/parallels/debug-page-broadcast.spec.ts index bc8b909bed..0b69c7967b 100644 --- a/tests/parallels/debug-page-broadcast.spec.ts +++ b/tests/parallels/debug-page-broadcast.spec.ts @@ -11,9 +11,9 @@ test('should broadcast a message to all debug pages', async ({ await page.waitForSelector('#__next'); await page2.waitForSelector('#__next'); await page.click('[data-testid="create-page"]'); - expect(await page.locator('tr').count()).toBe(2); - expect(await page2.locator('tr').count()).toBe(2); - await page2.click('[data-testid="create-page"]'); expect(await page.locator('tr').count()).toBe(3); expect(await page2.locator('tr').count()).toBe(3); + await page2.click('[data-testid="create-page"]'); + expect(await page.locator('tr').count()).toBe(4); + expect(await page2.locator('tr').count()).toBe(4); });