From 7aaec3ad517b07297296335dbd716d741c34652c Mon Sep 17 00:00:00 2001 From: Cats Juice Date: Thu, 18 Jan 2024 12:54:44 +0000 Subject: [PATCH] feat(core): add daily count for journal sidebar (#5559) --- .../editor-sidebar/extensions/journal.css.ts | 149 +++++++++++++ .../editor-sidebar/extensions/journal.tsx | 210 +++++++++++++++++- packages/frontend/i18n/src/resources/en.json | 6 +- 3 files changed, 354 insertions(+), 11 deletions(-) diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.css.ts b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.css.ts index 08cad05c6..728b17475 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.css.ts +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.css.ts @@ -1,5 +1,154 @@ import { style } from '@vanilla-extract/css'; +const interactive = style({ + position: 'relative', + + selectors: { + '&:hover': { + backgroundColor: 'var(--affine-hover-color)', + }, + '&::before': { + content: '""', + position: 'absolute', + inset: 0, + opacity: 0, + borderRadius: 'inherit', + boxShadow: '0 0 0 3px var(--affine-primary-color)', + pointerEvents: 'none', + }, + '&::after': { + content: '""', + position: 'absolute', + inset: 0, + borderRadius: 'inherit', + boxShadow: '0 0 0 0px var(--affine-primary-color)', + pointerEvents: 'none', + }, + '&:focus-visible::before': { + opacity: 0.2, + }, + '&:focus-visible::after': { + boxShadow: '0 0 0 1px var(--affine-primary-color)', + }, + }, +}); + export const calendar = style({ padding: '16px', }); + +export const journalPanel = style({ + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', +}); + +export const dailyCount = style({ + height: 0, + flexGrow: 1, + display: 'flex', + flexDirection: 'column', + gap: 8, +}); + +export const dailyCountHeader = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: '0 16px', + gap: 8, +}); + +export const dailyCountNav = style([ + interactive, + { + height: 28, + width: 0, + flex: 1, + fontWeight: 500, + fontSize: 14, + padding: '4px 8px', + whiteSpace: 'nowrap', + borderRadius: 8, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: 'var(--affine-text-secondary-color)', + transition: 'all .3s', + + selectors: { + '&[aria-selected="true"]': { + backgroundColor: 'var(--affine-background-tertiary-color)', + color: 'var(--affine-text-primary-color)', + }, + }, + }, +]); + +export const dailyCountContainer = style({ + height: 0, + flexGrow: 1, + display: 'flex', + width: `calc(var(--item-count) * 100%)`, + transition: 'transform .15s ease', + transform: + 'translateX(calc(var(--active-index) * 100% / var(--item-count) * -1))', +}); +export const dailyCountItem = style({ + width: 'calc(100% / var(--item-count))', + height: '100%', +}); +export const dailyCountContent = style({ + padding: '8px 16px', + display: 'flex', + flexDirection: 'column', + gap: 4, +}); +export const dailyCountEmpty = style({ + width: '100%', + height: '100%', + maxHeight: 220, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + + lineHeight: '24px', + fontSize: 15, + color: 'var(--affine-text-secondary-color)', + textAlign: 'center', + padding: '0 70px', + fontWeight: 400, +}); + +// page item +export const pageItem = style([ + interactive, + { + width: '100%', + display: 'flex', + alignItems: 'center', + borderRadius: 4, + padding: '0 4px', + gap: 8, + height: 30, + }, +]); +export const pageItemIcon = style({ + width: 20, + height: 20, + color: 'var(--affine-icon-color)', +}); +export const pageItemLabel = style({ + width: 0, + flex: 1, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + fontWeight: 500, + fontSize: 'var(--affine-font-size-sm)', + color: 'var(--affine-text-primary-color)', + textAlign: 'left', +}); diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx index 2fb63140c..b939f7e3d 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx @@ -1,16 +1,67 @@ -import { AFFiNEDatePicker } from '@affine/component'; +import { AFFiNEDatePicker, Scrollable } from '@affine/component'; import { useJournalHelper, useJournalInfoHelper, } from '@affine/core/hooks/use-journal'; -import { TodayIcon } from '@blocksuite/icons'; +import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { EdgelessIcon, PageIcon, TodayIcon } from '@blocksuite/icons'; +import type { Page } from '@blocksuite/store'; +import { assignInlineVars } from '@vanilla-extract/dynamic'; +import clsx from 'clsx'; import dayjs from 'dayjs'; -import { useCallback, useEffect, useState } from 'react'; +import type { HTMLAttributes, ReactNode } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { EditorExtension, EditorExtensionProps } from '..'; import * as styles from './journal.css'; -const EditorJournalPanel = ({ workspace, page }: EditorExtensionProps) => { +/** + * @internal + */ +const CountDisplay = ({ + count, + max = 99, + ...attrs +}: { count: number; max?: number } & HTMLAttributes) => { + return {count > max ? `${max}+` : count}; +}; +interface PageItemProps extends HTMLAttributes { + page: Page; + right?: ReactNode; +} +const PageItem = ({ page, right, className, ...attrs }: PageItemProps) => { + const { isJournal } = useJournalInfoHelper(page.meta); + + const icon = isJournal ? ( + + ) : page.meta.mode === 'edgeless' ? ( + + ) : ( + + ); + return ( + + ); +}; + +type NavItemName = 'createdToday' | 'updatedToday'; +interface NavItem { + name: NavItemName; + label: string; + count: number; +} + +const EditorJournalPanel = (props: EditorExtensionProps) => { + const { workspace, page } = props; const { journalDate } = useJournalInfoHelper(page?.meta); const { openJournal } = useJournalHelper(workspace); const [date, setDate] = useState(dayjs().format('YYYY-MM-DD')); @@ -28,12 +79,151 @@ const EditorJournalPanel = ({ workspace, page }: EditorExtensionProps) => { ); return ( - +
+ + + +
+ ); +}; + +const sortPagesByDate = ( + pages: Page[], + field: 'updatedDate' | 'createDate', + order: 'asc' | 'desc' = 'desc' +) => { + return [...pages].sort((a, b) => { + return ( + (order === 'asc' ? 1 : -1) * + dayjs(b.meta[field]).diff(dayjs(a.meta[field])) + ); + }); +}; + +const DailyCountEmptyFallback = ({ name }: { name: NavItemName }) => { + const t = useAFFiNEI18N(); + + return ( +
+ {name === 'createdToday' + ? t['com.affine.journal.daily-count-created-empty-tips']() + : t['com.affine.journal.daily-count-updated-empty-tips']()} +
+ ); +}; +const JournalDailyCountBlock = ({ workspace, page }: EditorExtensionProps) => { + const nodeRef = useRef(null); + const t = useAFFiNEI18N(); + const { journalDate } = useJournalInfoHelper(page?.meta); + const [activeItem, setActiveItem] = useState('createdToday'); + + const navigateHelper = useNavigateHelper(); + + const getTodaysPages = useCallback( + (field: 'createDate' | 'updatedDate') => { + const pages: Page[] = []; + Array.from(workspace.pages.values()).forEach(page => { + if (page.meta.trash) return; + if ( + page.meta[field] && + dayjs(page.meta[field]).isSame(journalDate, 'day') + ) { + pages.push(page); + } + }); + return sortPagesByDate(pages, field); + }, + [journalDate, workspace.pages] + ); + + const createdToday = useMemo( + () => getTodaysPages('createDate'), + [getTodaysPages] + ); + const updatedToday = useMemo( + () => getTodaysPages('updatedDate'), + [getTodaysPages] + ); + + const headerItems = useMemo( + () => [ + { + name: 'createdToday', + label: t['com.affine.journal.created-today'](), + count: createdToday.length, + }, + { + name: 'updatedToday', + label: t['com.affine.journal.updated-today'](), + count: updatedToday.length, + }, + ], + [createdToday.length, t, updatedToday.length] + ); + + const activeIndex = headerItems.findIndex(({ name }) => name === activeItem); + + const vars = assignInlineVars({ + '--active-index': String(activeIndex), + '--item-count': String(headerItems.length), + }); + + return ( +
+
+ {headerItems.map(({ label, count, name }, index) => { + return ( + + ); + })} +
+ +
+ {headerItems.map(({ name }) => { + const renderList = + name === 'createdToday' ? createdToday : updatedToday; + if (renderList.length === 0) + return ( +
+ +
+ ); + return ( + + + +
+ {renderList.map((page, index) => ( + + navigateHelper.openPage(workspace.id, page.id) + } + tabIndex={name === activeItem ? 0 : -1} + key={index} + page={page} + /> + ))} +
+
+
+ ); + })} +
+
); }; diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 1b7847b98..a85767935 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1043,5 +1043,9 @@ "com.affine.calendar.weekdays.wed": "Wed", "com.affine.calendar.weekdays.thu": "Thu", "com.affine.calendar.weekdays.fri": "Fri", - "com.affine.calendar.weekdays.sat": "Sat" + "com.affine.calendar.weekdays.sat": "Sat", + "com.affine.journal.created-today": "Created today", + "com.affine.journal.updated-today": "Updated today", + "com.affine.journal.daily-count-created-empty-tips": "You haven't created anything yet", + "com.affine.journal.daily-count-updated-empty-tips": "You haven't updated anything yet" }