diff --git a/packages/frontend/core/src/components/affine/page-history-modal/data.ts b/packages/frontend/core/src/components/affine/page-history-modal/data.ts index f656c3efc3..93f0a8d9ca 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/data.ts +++ b/packages/frontend/core/src/components/affine/page-history-modal/data.ts @@ -178,7 +178,11 @@ export const useSnapshotPage = ( export const historyListGroupByDay = (histories: DocHistory[]) => { const map = new Map(); for (const history of histories) { - const day = new Date(history.timestamp).toLocaleDateString(); + const day = new Date(history.timestamp).toLocaleDateString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); const list = map.get(day) ?? []; list.push(history); map.set(day, list); @@ -208,8 +212,8 @@ export const useRestorePage = (workspace: Workspace, pageId: string) => { // should also update the page title, since it may be changed in the history const title = page.meta.title; - if (getPageMeta(pageDocId)?.title !== title) { - setPageTitle(pageDocId, title); + if (getPageMeta(pageId)?.title !== title) { + setPageTitle(pageId, title); } await recover({ @@ -230,6 +234,7 @@ export const useRestorePage = (workspace: Workspace, pageId: string) => { getPageMeta, mutateQueryResource, page, + pageId, recover, setPageTitle, workspace.id, diff --git a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx index 5082cf0d78..2f2a58763f 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx @@ -1,4 +1,4 @@ -import { Scrollable } from '@affine/component'; +import { Loading, Scrollable } from '@affine/component'; import { BlockSuiteEditor, EditorLoading, @@ -8,11 +8,14 @@ import { ConfirmModal, Modal } from '@affine/component/ui/modal'; import type { PageMode } from '@affine/core/atoms'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom'; +import { ToggleCollapseIcon } from '@blocksuite/icons'; import type { Page, Workspace } from '@blocksuite/store'; +import * as Collapsible from '@radix-ui/react-collapsible'; import type { DialogContentProps } from '@radix-ui/react-dialog'; import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useAtom, useAtomValue } from 'jotai'; import { + Fragment, type PropsWithChildren, Suspense, useCallback, @@ -142,7 +145,9 @@ const HistoryEditorPreview = ({ onModeChange={onModeChange} /> ) : ( - +
+ +
)} ); @@ -167,6 +172,8 @@ const PageHistoryList = ({ return historyListGroupByDay(historyList); }, [historyList]); + const [collapsedMap, setCollapsedMap] = useState>({}); + const t = useAFFiNEI18N(); useLayoutEffect(() => { @@ -182,25 +189,57 @@ const PageHistoryList = ({ - {historyListByDay.map(([day, list]) => { + {historyListByDay.map(([day, list], i) => { + const collapsed = collapsedMap[i]; return ( -
-
{day}
- {list.map(history => ( + + + setCollapsedMap(prev => ({ ...prev, [i]: !collapsed })) + } + className={styles.historyItemGroupTitle} + >
{ - e.stopPropagation(); - onVersionChange(history.timestamp); - }} - data-active={activeVersion === history.timestamp} + data-testid="page-list-group-header-collapsed-button" + className={styles.collapsedIconContainer} > - +
- ))} -
+ {day} + + + {list.map((history, idx) => { + return ( + +
{ + e.stopPropagation(); + onVersionChange(history.timestamp); + }} + data-active={activeVersion === history.timestamp} + > + +
+ {idx > list.length - 1 ? ( +
+ ) : null} + + ); + })} + + ); })} {loadMore ? ( diff --git a/packages/frontend/core/src/components/affine/page-history-modal/styles.css.ts b/packages/frontend/core/src/components/affine/page-history-modal/styles.css.ts index e9d1f57659..5d0aec0419 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/styles.css.ts +++ b/packages/frontend/core/src/components/affine/page-history-modal/styles.css.ts @@ -71,6 +71,44 @@ export const editor = style({ overflow: 'hidden', }); +export const rowWrapper = style({ + display: 'flex', + height: '100%', + position: 'relative', + overflow: 'hidden', + ':before': { + content: '""', + width: 1, + height: '100%', + backgroundColor: 'var(--affine-border-color)', + position: 'absolute', + left: 16, + top: 0, + bottom: 0, + transform: 'translate(-50%)', + }, + selectors: { + '&:is(:last-of-type, :first-of-type):not(:last-of-type:first-of-type)::before': + { + height: '50%', + }, + '&:last-of-type:first-of-type::before': { + display: 'none', + }, + '&:first-of-type::before': { + top: '50%', + }, + }, +}); + +export const loadingContainer = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + backgroundColor: 'var(--affine-background-primary-color)', +}); + export const historyList = style({ overflow: 'hidden', height: '100%', @@ -85,7 +123,6 @@ export const historyListScrollable = style({ export const historyListScrollableInner = style({ display: 'flex', - gap: 16, flexDirection: 'column', }); @@ -102,32 +139,58 @@ export const historyListHeader = style({ export const historyItemGroup = style({ display: 'flex', flexDirection: 'column', - rowGap: 6, }); export const historyItemGroupTitle = style({ display: 'flex', alignItems: 'center', - padding: '12px', - fontWeight: 'bold', + padding: '0 12px 0 4px', + whiteSpace: 'nowrap', + color: 'var(--affine-text-secondary-color)', + gap: 4, backgroundColor: 'var(--affine-background-primary-color)', - position: 'sticky', - top: 0, -}); - -export const historyItem = style({ - display: 'flex', - alignItems: 'center', - padding: '0 12px', - height: 32, - cursor: 'pointer', - selectors: { - '&:hover, &[data-active=true]': { - backgroundColor: 'var(--affine-hover-color)', - }, + height: 28, + ':hover': { + background: 'var(--affine-hover-color)', }, }); +export const historyItem = style([ + rowWrapper, + { + display: 'flex', + alignItems: 'center', + padding: '0 32px', + height: 30, + cursor: 'pointer', + selectors: { + '&:hover, &[data-active=true]': { + backgroundColor: 'var(--affine-hover-color)', + }, + // draw circle + '&::after': { + content: '""', + width: 7, + height: 7, + backgroundColor: 'var(--affine-background-secondary-color)', + borderRadius: '50%', + border: '1px solid var(--affine-border-color)', + position: 'absolute', + left: 16, + top: '50%', + bottom: 0, + transform: 'translate(-50%, -50%)', + }, + '&[data-active=true]::after': { + backgroundColor: 'var(--affine-primary-color)', + borderColor: 'var(--affine-black-30)', + }, + }, + }, +]); + +export const historyItemGap = style([rowWrapper, { height: 16 }]); + export const historyItemLoadMore = style([ historyItem, { @@ -186,3 +249,27 @@ export const emptyHistoryPromptDescription = style({ fontSize: 'var(--affine-font-xs)', color: 'var(--affine-text-secondary-color)', }); + +export const collapsedIcon = style({ + transition: 'transform 0.2s ease-in-out', + selectors: { + '&[data-collapsed="false"]': { + transform: 'rotate(90deg)', + }, + }, +}); + +export const collapsedIconContainer = style({ + fontSize: 24, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '2px', + transition: 'transform 0.2s', + color: 'inherit', + selectors: { + '&[data-collapsed="true"]': { + transform: 'rotate(-90deg)', + }, + }, +});