mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-26 07:04:53 +03:00
feat: group all page by date (#2532)
Co-authored-by: Himself65 <himself65@outlook.com>
This commit is contained in:
parent
60057c666d
commit
7dcbe64d4e
@ -5,6 +5,7 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
|
import { DEFAULT_SORT_KEY } from "@affine/env/constant";
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { ArrowDownBigIcon, ArrowUpBigIcon } from '@blocksuite/icons';
|
import { ArrowDownBigIcon, ArrowUpBigIcon } from '@blocksuite/icons';
|
||||||
import { useMediaQuery, useTheme } from '@mui/material';
|
import { useMediaQuery, useTheme } from '@mui/material';
|
||||||
@ -111,7 +112,7 @@ export const PageList = ({
|
|||||||
}: PageListProps) => {
|
}: PageListProps) => {
|
||||||
const sorter = useSorter<ListData>({
|
const sorter = useSorter<ListData>({
|
||||||
data: list,
|
data: list,
|
||||||
key: 'updatedDate',
|
key: DEFAULT_SORT_KEY,
|
||||||
order: 'desc',
|
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 (
|
return (
|
||||||
<StyledTableContainer>
|
<StyledTableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
@ -139,6 +148,7 @@ export const PageList = ({
|
|||||||
/>
|
/>
|
||||||
<AllPagesBody
|
<AllPagesBody
|
||||||
isPublicWorkspace={isPublicWorkspace}
|
isPublicWorkspace={isPublicWorkspace}
|
||||||
|
groupKey={groupKey}
|
||||||
data={sorter.data}
|
data={sorter.data}
|
||||||
/>
|
/>
|
||||||
</Table>
|
</Table>
|
||||||
|
@ -1,29 +1,35 @@
|
|||||||
import { TableBody, TableCell } from '@affine/component';
|
import { TableBody, TableCell } from '@affine/component';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { useMediaQuery, useTheme } from '@mui/material';
|
import { useMediaQuery, useTheme } from '@mui/material';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
|
||||||
import { FavoriteTag } from './components/favorite-tag';
|
import { FavoriteTag } from './components/favorite-tag';
|
||||||
import { TitleCell } from './components/title-cell';
|
import { TitleCell } from './components/title-cell';
|
||||||
import { OperationCell } from './operation-cell';
|
import { OperationCell } from './operation-cell';
|
||||||
import { StyledTableRow } from './styles';
|
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';
|
import { formatDate } from './utils';
|
||||||
|
|
||||||
export const AllPagesBody = ({
|
export const AllPagesBody = ({
|
||||||
isPublicWorkspace,
|
isPublicWorkspace,
|
||||||
data,
|
data,
|
||||||
|
groupKey,
|
||||||
}: {
|
}: {
|
||||||
isPublicWorkspace: boolean;
|
isPublicWorkspace: boolean;
|
||||||
data: ListData[];
|
data: ListData[];
|
||||||
|
groupKey?: DateKey;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallDevices = useMediaQuery(theme.breakpoints.down('sm'));
|
const isSmallDevices = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const dataWithGroup = useDateGroup({ data, key: groupKey });
|
||||||
return (
|
return (
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.map(
|
{dataWithGroup.map(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
|
groupName,
|
||||||
pageId,
|
pageId,
|
||||||
title,
|
title,
|
||||||
icon,
|
icon,
|
||||||
@ -40,60 +46,74 @@ export const AllPagesBody = ({
|
|||||||
index
|
index
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
<StyledTableRow
|
<Fragment key={pageId}>
|
||||||
data-testid={`page-list-item-${pageId}`}
|
{groupName &&
|
||||||
key={`${pageId}-${index}`}
|
(index === 0 ||
|
||||||
>
|
dataWithGroup[index - 1].groupName !== groupName) && (
|
||||||
<TitleCell
|
<StyledTableRow>
|
||||||
icon={icon}
|
<TableCell
|
||||||
text={title || t['Untitled']()}
|
style={{
|
||||||
data-testid="title"
|
color: 'var(--affine-text-secondary-color)',
|
||||||
onClick={onClickPage}
|
background: 'initial',
|
||||||
/>
|
cursor: 'default',
|
||||||
<TableCell
|
}}
|
||||||
data-testid="created-date"
|
>
|
||||||
ellipsis={true}
|
{groupName}
|
||||||
hidden={isSmallDevices}
|
</TableCell>
|
||||||
onClick={onClickPage}
|
</StyledTableRow>
|
||||||
>
|
)}
|
||||||
{formatDate(createDate)}
|
<StyledTableRow data-testid={`page-list-item-${pageId}`}>
|
||||||
</TableCell>
|
<TitleCell
|
||||||
<TableCell
|
icon={icon}
|
||||||
data-testid="updated-date"
|
text={title || t['Untitled']()}
|
||||||
ellipsis={true}
|
data-testid="title"
|
||||||
hidden={isSmallDevices}
|
onClick={onClickPage}
|
||||||
onClick={onClickPage}
|
/>
|
||||||
>
|
|
||||||
{formatDate(updatedDate ?? createDate)}
|
|
||||||
</TableCell>
|
|
||||||
{!isPublicWorkspace && (
|
|
||||||
<TableCell
|
<TableCell
|
||||||
style={{
|
data-testid="created-date"
|
||||||
padding: 0,
|
ellipsis={true}
|
||||||
display: 'flex',
|
hidden={isSmallDevices}
|
||||||
justifyContent: 'center',
|
onClick={onClickPage}
|
||||||
alignItems: 'center',
|
|
||||||
gap: '10px',
|
|
||||||
}}
|
|
||||||
data-testid={`more-actions-${pageId}`}
|
|
||||||
>
|
>
|
||||||
<FavoriteTag
|
{formatDate(createDate)}
|
||||||
className={favorite ? '' : 'favorite-button'}
|
|
||||||
onClick={bookmarkPage}
|
|
||||||
active={!!favorite}
|
|
||||||
/>
|
|
||||||
<OperationCell
|
|
||||||
title={title}
|
|
||||||
favorite={favorite}
|
|
||||||
isPublic={isPublicPage}
|
|
||||||
onOpenPageInNewTab={onOpenPageInNewTab}
|
|
||||||
onToggleFavoritePage={bookmarkPage}
|
|
||||||
onRemoveToTrash={removeToTrash}
|
|
||||||
onDisablePublicSharing={onDisablePublicSharing}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
<TableCell
|
||||||
</StyledTableRow>
|
data-testid="updated-date"
|
||||||
|
ellipsis={true}
|
||||||
|
hidden={isSmallDevices}
|
||||||
|
onClick={onClickPage}
|
||||||
|
>
|
||||||
|
{formatDate(updatedDate ?? createDate)}
|
||||||
|
</TableCell>
|
||||||
|
{!isPublicWorkspace && (
|
||||||
|
<TableCell
|
||||||
|
style={{
|
||||||
|
padding: 0,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
}}
|
||||||
|
data-testid={`more-actions-${pageId}`}
|
||||||
|
>
|
||||||
|
<FavoriteTag
|
||||||
|
className={favorite ? '' : 'favorite-button'}
|
||||||
|
onClick={bookmarkPage}
|
||||||
|
active={!!favorite}
|
||||||
|
/>
|
||||||
|
<OperationCell
|
||||||
|
title={title}
|
||||||
|
favorite={favorite}
|
||||||
|
isPublic={isPublicPage}
|
||||||
|
onOpenPageInNewTab={onOpenPageInNewTab}
|
||||||
|
onToggleFavoritePage={bookmarkPage}
|
||||||
|
onRemoveToTrash={removeToTrash}
|
||||||
|
onDisablePublicSharing={onDisablePublicSharing}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</StyledTableRow>
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
@ -71,7 +71,12 @@ export const AllPageListMobileView = ({
|
|||||||
createNewPage={createNewPage}
|
createNewPage={createNewPage}
|
||||||
createNewEdgeless={createNewEdgeless}
|
createNewEdgeless={createNewEdgeless}
|
||||||
/>
|
/>
|
||||||
<AllPagesBody isPublicWorkspace={isPublicWorkspace} data={list} />
|
<AllPagesBody
|
||||||
|
isPublicWorkspace={isPublicWorkspace}
|
||||||
|
data={list}
|
||||||
|
// update groupKey after support sort by create date
|
||||||
|
groupKey="updatedDate"
|
||||||
|
/>
|
||||||
</Table>
|
</Table>
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
);
|
);
|
||||||
|
@ -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<T, V> = {
|
||||||
|
[K in keyof T]-?: T[K] extends V ? K : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
export type ListData = {
|
export type ListData = {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
@ -13,6 +22,8 @@ export type ListData = {
|
|||||||
onDisablePublicSharing: () => void;
|
onDisablePublicSharing: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DateKey = KeysMatching<ListData, Date>;
|
||||||
|
|
||||||
export type TrashListData = {
|
export type TrashListData = {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
@ -1,11 +1,51 @@
|
|||||||
const isToday = (date: Date) => {
|
export function isToday(date: Date): boolean {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
return (
|
return (
|
||||||
date.getDate() == today.getDate() &&
|
date.getDate() == today.getDate() &&
|
||||||
date.getMonth() == today.getMonth() &&
|
date.getMonth() == today.getMonth() &&
|
||||||
date.getFullYear() == today.getFullYear()
|
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 => {
|
export const formatDate = (date: Date): string => {
|
||||||
// yyyy-mm-dd MM-DD HH:mm
|
// yyyy-mm-dd MM-DD HH:mm
|
||||||
|
@ -79,7 +79,7 @@ AffineAllPageList.args = {
|
|||||||
removeToTrash: () => toast('Remove to trash'),
|
removeToTrash: () => toast('Remove to trash'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pageId: '1',
|
pageId: '3',
|
||||||
favorite: false,
|
favorite: false,
|
||||||
icon: <PageIcon />,
|
icon: <PageIcon />,
|
||||||
isPublicPage: true,
|
isPublicPage: true,
|
||||||
|
1
packages/env/src/constant.ts
vendored
1
packages/env/src/constant.ts
vendored
@ -5,6 +5,7 @@ export const DEFAULT_WORKSPACE_NAME = 'Demo Workspace';
|
|||||||
export const UNTITLED_WORKSPACE_NAME = 'Untitled';
|
export const UNTITLED_WORKSPACE_NAME = 'Untitled';
|
||||||
export const DEFAULT_HELLO_WORLD_PAGE_ID = 'hello-world';
|
export const DEFAULT_HELLO_WORLD_PAGE_ID = 'hello-world';
|
||||||
|
|
||||||
|
export const DEFAULT_SORT_KEY = 'updatedDate';
|
||||||
export const enum MessageCode {
|
export const enum MessageCode {
|
||||||
loginError,
|
loginError,
|
||||||
noPermission,
|
noPermission,
|
||||||
|
@ -279,6 +279,12 @@
|
|||||||
"com.affine.updater.update-available": "Update available",
|
"com.affine.updater.update-available": "Update available",
|
||||||
"com.affine.updater.open-download-page": "Open download page",
|
"com.affine.updater.open-download-page": "Open download page",
|
||||||
"com.affine.updater.restart-to-update": "Restart to install update",
|
"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",
|
"com.affine.workspace.cannot-delete": "You cannot delete the last workspace",
|
||||||
"FILE_ALREADY_EXISTS": "File already exists",
|
"FILE_ALREADY_EXISTS": "File already exists",
|
||||||
"others": "Others",
|
"others": "Others",
|
||||||
|
@ -62,7 +62,7 @@ test('access public workspace page', async ({ page, browser }) => {
|
|||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
});
|
});
|
||||||
await clickSideBarAllPageButton(page);
|
await clickSideBarAllPageButton(page);
|
||||||
await page.locator('tr').nth(1).click();
|
await page.locator('tr').nth(2).click();
|
||||||
const url = page.url();
|
const url = page.url();
|
||||||
const context = await browser.newContext();
|
const context = await browser.newContext();
|
||||||
const page2 = await context.newPage();
|
const page2 = await context.newPage();
|
||||||
|
@ -11,9 +11,9 @@ test('should broadcast a message to all debug pages', async ({
|
|||||||
await page.waitForSelector('#__next');
|
await page.waitForSelector('#__next');
|
||||||
await page2.waitForSelector('#__next');
|
await page2.waitForSelector('#__next');
|
||||||
await page.click('[data-testid="create-page"]');
|
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 page.locator('tr').count()).toBe(3);
|
||||||
expect(await page2.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);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user