feat(core): journal hooks and page header layout (#5549)

feat(core): split page header items

feat(core): journal page judgment and header layout

feat(core): Add journal today button and style changes to share menu
This commit is contained in:
Cats Juice 2024-01-18 07:17:14 +00:00
parent a64854319e
commit cabedef426
No known key found for this signature in database
GPG Key ID: 1C1E76924FAFDDE4
16 changed files with 465 additions and 281 deletions

View File

@ -13,9 +13,14 @@ import { ShareMenu } from './share-menu';
type SharePageModalProps = {
workspace: Workspace;
page: Page;
isJournal?: boolean;
};
export const SharePageButton = ({ workspace, page }: SharePageModalProps) => {
export const SharePageButton = ({
workspace,
page,
isJournal,
}: SharePageModalProps) => {
const [open, setOpen] = useState(false);
const { openPage } = useNavigateHelper();
@ -35,6 +40,7 @@ export const SharePageButton = ({ workspace, page }: SharePageModalProps) => {
return (
<>
<ShareMenu
isJournal={isJournal}
workspaceMetadata={workspace.meta}
currentPage={page}
onEnableAffineCloud={() => setOpen(true)}

View File

@ -157,3 +157,8 @@ globalStyle(`${shareLinkStyle} > span`, {
globalStyle(`${shareLinkStyle} > div > svg`, {
color: 'var(--affine-link-color)',
});
export const journalShareButton = style({
height: 32,
padding: '0px 8px',
});

View File

@ -6,6 +6,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { WorkspaceMetadata } from '@affine/workspace';
import { WebIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import clsx from 'clsx';
import { useIsSharedPage } from '../../../../hooks/affine/use-is-shared-page';
import * as styles from './index.css';
@ -15,6 +16,7 @@ import { SharePage } from './share-page';
export interface ShareMenuProps {
workspaceMetadata: WorkspaceMetadata;
currentPage: Page;
isJournal?: boolean;
onEnableAffineCloud: () => void;
}
@ -50,7 +52,11 @@ const LocalShareMenu = (props: ShareMenuProps) => {
modal: false,
}}
>
<Button data-testid="local-share-menu-button" type="primary">
<Button
className={clsx({ [styles.journalShareButton]: props.isJournal })}
data-testid="local-share-menu-button"
type="primary"
>
{t['com.affine.share-menu.shareButton']()}
</Button>
</Menu>
@ -76,7 +82,11 @@ const CloudShareMenu = (props: ShareMenuProps) => {
modal: false,
}}
>
<Button data-testid="cloud-share-menu-button" type="primary">
<Button
className={clsx({ [styles.journalShareButton]: props.isJournal })}
data-testid="cloud-share-menu-button"
type="primary"
>
{isSharedPage
? t['com.affine.share-menu.sharedButton']()
: t['com.affine.share-menu.shareButton']()}

View File

@ -1,159 +0,0 @@
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@affine/core/hooks/use-block-suite-page-meta';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import {
type FocusEvent,
type InputHTMLAttributes,
type KeyboardEvent,
useCallback,
useEffect,
useState,
} from 'react';
import type { PageMode } from '../../../atoms';
import { EditorModeSwitch } from '../block-suite-mode-switch';
import { PageHeaderMenuButton } from './operation-menu';
import * as styles from './styles.css';
export interface BlockSuiteHeaderTitleProps {
blockSuiteWorkspace: BlockSuiteWorkspace;
pageId: string;
isPublic?: boolean;
publicMode?: PageMode;
}
const EditableTitle = ({
value,
onFocus: propsOnFocus,
...inputProps
}: InputHTMLAttributes<HTMLInputElement>) => {
const onFocus = useCallback(
(e: FocusEvent<HTMLInputElement>) => {
e.target.select();
propsOnFocus?.(e);
},
[propsOnFocus]
);
return (
<div className={styles.headerTitleContainer}>
<input
className={styles.titleInput}
autoFocus={true}
value={value}
type="text"
data-testid="title-content"
onFocus={onFocus}
{...inputProps}
/>
<span className={styles.shadowTitle}>{value}</span>
</div>
);
};
const StableTitle = ({
blockSuiteWorkspace: workspace,
pageId,
onRename,
isPublic,
publicMode,
}: BlockSuiteHeaderTitleProps & {
onRename?: () => void;
}) => {
const currentPage = workspace.getPage(pageId);
const pageMeta = useBlockSuitePageMeta(workspace).find(
meta => meta.id === currentPage?.id
);
const title = pageMeta?.title;
const handleRename = useCallback(() => {
if (!isPublic && onRename) {
onRename();
}
}, [isPublic, onRename]);
return (
<div className={styles.headerTitleContainer}>
<EditorModeSwitch
blockSuiteWorkspace={workspace}
pageId={pageId}
isPublic={isPublic}
publicMode={publicMode}
/>
<span
data-testid="title-edit-button"
className={styles.titleEditButton}
onDoubleClick={handleRename}
>
{title || 'Untitled'}
</span>
{isPublic ? null : (
<PageHeaderMenuButton rename={onRename} pageId={pageId} />
)}
</div>
);
};
const BlockSuiteTitleWithRename = (props: BlockSuiteHeaderTitleProps) => {
const { blockSuiteWorkspace: workspace, pageId } = props;
const currentPage = workspace.getPage(pageId);
const pageMeta = useBlockSuitePageMeta(workspace).find(
meta => meta.id === currentPage?.id
);
const pageTitleMeta = usePageMetaHelper(workspace);
const [isEditable, setIsEditable] = useState(false);
const [title, setPageTitle] = useState(pageMeta?.title || 'Untitled');
const onRename = useCallback(() => {
setIsEditable(true);
}, []);
const onBlur = useCallback(() => {
setIsEditable(false);
if (!currentPage?.id) {
return;
}
pageTitleMeta.setPageTitle(currentPage.id, title);
}, [currentPage?.id, pageTitleMeta, title]);
const handleKeyDown = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' || e.key === 'Escape') {
onBlur();
}
},
[onBlur]
);
useEffect(() => {
setPageTitle(pageMeta?.title || '');
}, [pageMeta?.title]);
if (isEditable) {
return (
<EditableTitle
onBlur={onBlur}
value={title}
onKeyDown={handleKeyDown}
onChange={e => {
const value = e.target.value;
setPageTitle(value);
}}
/>
);
}
return <StableTitle {...props} onRename={onRename} />;
};
export const BlockSuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
if (props.isPublic) {
return <StableTitle {...props} />;
}
return <BlockSuiteTitleWithRename {...props} />;
};
BlockSuiteHeaderTitle.displayName = 'BlockSuiteHeaderTitle';

View File

@ -1,43 +0,0 @@
import { type ComplexStyleRule, style } from '@vanilla-extract/css';
export const headerTitleContainer = style({
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
flexGrow: 1,
position: 'relative',
overflow: 'hidden',
columnGap: 12,
});
export const titleEditButton = style({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
WebkitAppRegion: 'no-drag',
} as ComplexStyleRule);
export const titleInput = style({
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
margin: 'auto',
width: '100%',
height: '100%',
selectors: {
'&:focus': {
border: '1px solid var(--affine-black-10)',
borderRadius: '8px',
height: '32px',
padding: '6px 8px',
borderColor: 'var(--affine-primary-color)',
boxShadow: 'var(--affine-active-shadow)',
},
},
});
export const shadowTitle = style({
visibility: 'hidden',
});

View File

@ -0,0 +1,46 @@
import { FavoriteTag } from '@affine/core/components/page-list';
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
import { toast } from '@affine/core/utils';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
export interface FavoriteButtonProps {
pageId: string;
}
export const useFavorite = (pageId: string) => {
const t = useAFFiNEI18N();
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
const currentPage = blockSuiteWorkspace.getPage(pageId);
assertExists(currentPage);
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
);
const favorite = pageMeta?.favorite ?? false;
const { toggleFavorite: _toggleFavorite } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);
const toggleFavorite = useCallback(() => {
_toggleFavorite(pageId);
toast(
favorite
? t['com.affine.toastMessage.removedFavorites']()
: t['com.affine.toastMessage.addedFavorites']()
);
}, [favorite, pageId, t, _toggleFavorite]);
return { favorite, toggleFavorite };
};
export const FavoriteButton = ({ pageId }: FavoriteButtonProps) => {
const { favorite, toggleFavorite } = useFavorite(pageId);
return <FavoriteTag active={!!favorite} onClick={toggleFavorite} />;
};

View File

@ -0,0 +1,42 @@
import { WeekDatePicker, type WeekDatePickerHandle } from '@affine/component';
import {
useJournalHelper,
useJournalInfoHelper,
} from '@affine/core/hooks/use-journal';
import type { BlockSuiteWorkspace } from '@affine/core/shared';
import type { Page } from '@blocksuite/store';
import dayjs from 'dayjs';
import { useEffect, useRef, useState } from 'react';
export interface JournalWeekDatePickerProps {
workspace: BlockSuiteWorkspace;
page: Page;
}
const weekStyle = { maxWidth: 548, width: '100%' };
export const JournalWeekDatePicker = ({
workspace,
page,
}: JournalWeekDatePickerProps) => {
const handleRef = useRef<WeekDatePickerHandle>(null);
const { journalDate } = useJournalInfoHelper(page.meta);
const { openJournal } = useJournalHelper(workspace);
const [date, setDate] = useState(
(journalDate ?? dayjs()).format('YYYY-MM-DD')
);
useEffect(() => {
if (!journalDate) return;
setDate(journalDate.format('YYYY-MM-DD'));
handleRef.current?.setCursor?.(journalDate);
}, [journalDate]);
return (
<WeekDatePicker
handleRef={handleRef}
style={weekStyle}
value={date}
onChange={openJournal}
/>
);
};

View File

@ -0,0 +1,28 @@
import { Button } from '@affine/component';
import { useJournalHelper } from '@affine/core/hooks/use-journal';
import type { BlockSuiteWorkspace } from '@affine/core/shared';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useCallback } from 'react';
export interface JournalTodayButtonProps {
workspace: BlockSuiteWorkspace;
}
export const JournalTodayButton = ({ workspace }: JournalTodayButtonProps) => {
const t = useAFFiNEI18N();
const journalHelper = useJournalHelper(workspace);
const onToday = useCallback(() => {
journalHelper.openToday();
}, [journalHelper]);
return (
<Button
size="default"
onClick={onToday}
style={{ height: 32, padding: '0px 8px' }}
>
{t['com.affine.today']()}
</Button>
);
};

View File

@ -4,13 +4,15 @@ import {
MenuItem,
MenuSeparator,
} from '@affine/component/ui/menu';
import {
Export,
FavoriteTag,
MoveToTrash,
} from '@affine/core/components/page-list';
import { currentModeAtom } from '@affine/core/atoms/mode';
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
import { Export, MoveToTrash } from '@affine/core/components/page-list';
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
import { toast } from '@affine/core/utils';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
@ -27,21 +29,21 @@ import {
import { useAtomValue } from 'jotai';
import { useCallback, useState } from 'react';
import { currentModeAtom } from '../../../atoms/mode';
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import { useExportPage } from '../../../hooks/affine/use-export-page';
import { useTrashModalHelper } from '../../../hooks/affine/use-trash-modal-helper';
import { toast } from '../../../utils';
import { PageHistoryModal } from '../../affine/page-history-modal/history-modal';
import { HeaderDropDownButton } from '../../pure/header-drop-down-button';
import { usePageHelper } from '../block-suite-page-list/utils';
import { HeaderDropDownButton } from '../../../pure/header-drop-down-button';
import { usePageHelper } from '../../block-suite-page-list/utils';
import { useFavorite } from '../favorite';
type PageMenuProps = {
rename?: () => void;
pageId: string;
isJournal?: boolean;
};
// fixme: refactor this file
export const PageHeaderMenuButton = ({ rename, pageId }: PageMenuProps) => {
export const PageHeaderMenuButton = ({
rename,
pageId,
isJournal,
}: PageMenuProps) => {
const t = useAFFiNEI18N();
// fixme(himself65): remove these hooks ASAP
@ -54,9 +56,10 @@ export const PageHeaderMenuButton = ({ rename, pageId }: PageMenuProps) => {
meta => meta.id === pageId
);
const currentMode = useAtomValue(currentModeAtom);
const favorite = pageMeta?.favorite ?? false;
const { togglePageMode, toggleFavorite, duplicate } =
const { favorite, toggleFavorite } = useFavorite(pageId);
const { togglePageMode, duplicate } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);
const { importFile } = usePageHelper(blockSuiteWorkspace);
const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace);
@ -78,14 +81,6 @@ export const PageHeaderMenuButton = ({ rename, pageId }: PageMenuProps) => {
});
}, [pageId, pageMeta, setTrashModal]);
const handleFavorite = useCallback(() => {
toggleFavorite(pageId);
toast(
favorite
? t['com.affine.toastMessage.removedFavorites']()
: t['com.affine.toastMessage.addedFavorites']()
);
}, [favorite, pageId, t, toggleFavorite]);
const handleSwitchMode = useCallback(() => {
togglePageMode(pageId);
toast(
@ -107,18 +102,20 @@ export const PageHeaderMenuButton = ({ rename, pageId }: PageMenuProps) => {
const EditMenu = (
<>
<MenuItem
preFix={
<MenuIcon>
<EditIcon />
</MenuIcon>
}
data-testid="editor-option-menu-rename"
onSelect={rename}
style={menuItemStyle}
>
{t['Rename']()}
</MenuItem>
{!isJournal && (
<MenuItem
preFix={
<MenuIcon>
<EditIcon />
</MenuIcon>
}
data-testid="editor-option-menu-rename"
onSelect={rename}
style={menuItemStyle}
>
{t['Rename']()}
</MenuItem>
)}
<MenuItem
preFix={
<MenuIcon>
@ -136,7 +133,7 @@ export const PageHeaderMenuButton = ({ rename, pageId }: PageMenuProps) => {
</MenuItem>
<MenuItem
data-testid="editor-option-menu-favorite"
onSelect={handleFavorite}
onSelect={toggleFavorite}
style={menuItemStyle}
preFix={
<MenuIcon>
@ -162,18 +159,20 @@ export const PageHeaderMenuButton = ({ rename, pageId }: PageMenuProps) => {
{t['com.affine.header.option.add-tag']()}
</MenuItem> */}
<MenuSeparator />
<MenuItem
preFix={
<MenuIcon>
<DuplicateIcon />
</MenuIcon>
}
data-testid="editor-option-menu-duplicate"
onSelect={handleDuplicate}
style={menuItemStyle}
>
{t['com.affine.header.option.duplicate']()}
</MenuItem>
{!isJournal && (
<MenuItem
preFix={
<MenuIcon>
<DuplicateIcon />
</MenuIcon>
}
data-testid="editor-option-menu-duplicate"
onSelect={handleDuplicate}
style={menuItemStyle}
>
{t['com.affine.header.option.duplicate']()}
</MenuItem>
)}
<MenuItem
preFix={
<MenuIcon>
@ -232,7 +231,6 @@ export const PageHeaderMenuButton = ({ rename, pageId }: PageMenuProps) => {
onOpenChange={setHistoryModalOpen}
/>
) : null}
<FavoriteTag active={!!pageMeta?.favorite} onClick={handleFavorite} />
</>
);
};

View File

@ -0,0 +1,57 @@
import { InlineEdit, type InlineEditProps } from '@affine/component';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@affine/core/hooks/use-block-suite-page-meta';
import type { BlockSuiteWorkspace } from '@affine/core/shared';
import type { HTMLAttributes } from 'react';
import { useCallback } from 'react';
import * as styles from './style.css';
export interface BlockSuiteHeaderTitleProps {
blockSuiteWorkspace: BlockSuiteWorkspace;
pageId: string;
/** if set, title cannot be edited */
isPublic?: boolean;
inputHandleRef?: InlineEditProps['handleRef'];
}
const inputAttrs = {
'data-testid': 'title-content',
} as HTMLAttributes<HTMLInputElement>;
export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
const {
blockSuiteWorkspace: workspace,
pageId,
isPublic,
inputHandleRef,
} = props;
const currentPage = workspace.getPage(pageId);
const pageMeta = useBlockSuitePageMeta(workspace).find(
meta => meta.id === currentPage?.id
);
const title = pageMeta?.title;
const { setPageTitle } = usePageMetaHelper(workspace);
const onChange = useCallback(
(v: string) => {
setPageTitle(currentPage?.id || '', v);
},
[currentPage?.id, setPageTitle]
);
return (
<InlineEdit
className={styles.title}
autoSelect
value={title}
onChange={onChange}
editable={!isPublic}
placeholder="Untitled"
data-testid="title-edit-button"
handleRef={inputHandleRef}
inputAttrs={inputAttrs}
/>
);
};

View File

@ -0,0 +1,6 @@
import { style } from '@vanilla-extract/css';
export const title = style({
fontWeight: 500,
color: 'var(--affine-text-primary-color)',
});

View File

@ -122,5 +122,4 @@ export const headerDivider = style({
height: '20px',
width: '1px',
background: 'var(--affine-border-color)',
margin: '0 12px',
});

View File

@ -0,0 +1,113 @@
import type { PageMeta } from '@blocksuite/store';
import { initEmptyPage } from '@toeverything/infra/blocksuite';
import dayjs from 'dayjs';
import { useCallback, useMemo } from 'react';
import type { BlockSuiteWorkspace } from '../shared';
import { useBlockSuiteWorkspaceHelper } from './use-block-suite-workspace-helper';
import { useNavigateHelper } from './use-navigate-helper';
type MaybeDate = Date | string | number;
export const JOURNAL_DATE_FORMAT = 'YYYY-MM-DD';
function isPageJournal(pageMeta?: PageMeta) {
return !!(pageMeta && pageMeta.title.match(/^\d{4}-\d{2}-\d{2}$/));
}
function getJournalDate(pageMeta?: PageMeta) {
if (!isPageJournal(pageMeta)) return null;
if (!pageMeta?.title) return null;
if (!dayjs(pageMeta.title).isValid()) return null;
return dayjs(pageMeta.title);
}
export const useJournalHelper = (workspace: BlockSuiteWorkspace) => {
const bsWorkspaceHelper = useBlockSuiteWorkspaceHelper(workspace);
const navigateHelper = useNavigateHelper();
/**
* @internal
*/
const _createJournal = useCallback(
(maybeDate: MaybeDate) => {
const title = dayjs(maybeDate).format(JOURNAL_DATE_FORMAT);
const page = bsWorkspaceHelper.createPage();
initEmptyPage(page, title).catch(err =>
console.error('Failed to load journal page', err)
);
return page;
},
[bsWorkspaceHelper]
);
/**
* query all journals by date
*/
const getJournalsByDate = useCallback(
(maybeDate: MaybeDate) => {
const day = dayjs(maybeDate);
return Array.from(workspace.pages.values()).filter(page => {
if (!isPageJournal(page.meta)) return false;
if (page.meta.trash) return false;
const journalDate = getJournalDate(page.meta);
if (!journalDate) return false;
return day.isSame(journalDate, 'day');
});
},
[workspace.pages]
);
/**
* get journal by date, create one if not exist
*/
const getJournalByDate = useCallback(
(maybeDate: MaybeDate) => {
const pages = getJournalsByDate(maybeDate);
if (pages.length) return pages[0];
return _createJournal(maybeDate);
},
[_createJournal, getJournalsByDate]
);
/**
* open journal by date, create one if not exist
*/
const openJournal = useCallback(
(maybeDate: MaybeDate) => {
const page = getJournalByDate(maybeDate);
navigateHelper.openPage(workspace.id, page.id);
},
[getJournalByDate, navigateHelper, workspace.id]
);
/**
* open today's journal
*/
const openToday = useCallback(() => {
const date = dayjs().format(JOURNAL_DATE_FORMAT);
openJournal(date);
}, [openJournal]);
return useMemo(
() => ({
getJournalsByDate,
getJournalByDate,
openJournal,
openToday,
}),
[getJournalByDate, getJournalsByDate, openJournal, openToday]
);
};
export const useJournalInfoHelper = (pageMeta?: PageMeta) => {
const isJournal = isPageJournal(pageMeta);
const journalDate = useMemo(() => getJournalDate(pageMeta), [pageMeta]);
return useMemo(
() => ({
isJournal,
journalDate,
}),
[isJournal, journalDate]
);
};

View File

@ -1,7 +1,8 @@
import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite-mode-switch';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { PageMode } from '../../atoms';
import { BlockSuiteHeaderTitle } from '../../components/blocksuite/block-suite-header-title';
import { BlocksuiteHeaderTitle } from '../../components/blocksuite/block-suite-header/title/index';
import ShareHeaderLeftItem from '../../components/cloud/share-header-left-item';
import ShareHeaderRightItem from '../../components/cloud/share-header-right-item';
import { Header } from '../../components/pure/header';
@ -20,12 +21,19 @@ export function ShareHeader({
isFloat={publishMode === 'edgeless'}
left={<ShareHeaderLeftItem />}
center={
<BlockSuiteHeaderTitle
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
isPublic={true}
publicMode={publishMode}
/>
<>
<EditorModeSwitch
isPublic
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
publicMode={publishMode}
/>
<BlocksuiteHeaderTitle
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
isPublic={true}
/>
</>
}
right={
<ShareHeaderRightItem

View File

@ -28,6 +28,7 @@ export const mainHeader = style([
header,
{
padding: '0 16px',
gap: 12,
},
]);
@ -58,3 +59,11 @@ export const standaloneExtensionSwitcherWrapper = style({
height: '52px',
position: 'relative',
});
export const journalWeekPicker = style({
minWidth: 100,
flexGrow: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});

View File

@ -1,16 +1,24 @@
import type { InlineEditHandle } from '@affine/component';
import { IconButton } from '@affine/component';
import {
appSidebarFloatingAtom,
appSidebarOpenAtom,
SidebarSwitch,
} from '@affine/component/app-sidebar';
import { FavoriteButton } from '@affine/core/components/blocksuite/block-suite-header/favorite';
import { JournalWeekDatePicker } from '@affine/core/components/blocksuite/block-suite-header/journal/date-picker';
import { JournalTodayButton } from '@affine/core/components/blocksuite/block-suite-header/journal/today-button';
import { PageHeaderMenuButton } from '@affine/core/components/blocksuite/block-suite-header/menu';
import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite-mode-switch';
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
import type { Workspace } from '@affine/workspace';
import { RightSidebarIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback, useRef } from 'react';
import { SharePageButton } from '../../../components/affine/share-page-modal';
import { BlockSuiteHeaderTitle } from '../../../components/blocksuite/block-suite-header-title';
import { BlocksuiteHeaderTitle } from '../../../components/blocksuite/block-suite-header/title/index';
import { HeaderDivider } from '../../../components/pure/header';
import { WindowsAppControls } from '../../../components/pure/header/windows-app-controls';
import * as styles from './detail-page-header.css';
@ -46,12 +54,7 @@ const WindowsMainPageHeaderRight = ({
return (
<>
<HeaderDivider />
<div
className={styles.mainHeaderRight}
style={{
marginRight: editorSidebarOpen ? 0 : -16,
}}
>
<div className={styles.mainHeaderRight} style={{ marginRight: -16 }}>
{showSidebarSwitch ? <ToggleSidebarButton /> : null}
<WindowsAppControls />
</div>
@ -100,27 +103,71 @@ function Header({
);
}
export function DetailPageHeader({
page,
workspace,
showSidebarSwitch = true,
}: {
interface PageHeaderProps {
page: Page;
workspace: Workspace;
showSidebarSwitch?: boolean;
}) {
}
const RightHeader = isWindowsDesktop
? WindowsMainPageHeaderRight
: NonWindowsMainPageHeaderRight;
export function JournalPageHeader({
page,
workspace,
showSidebarSwitch = true,
}: PageHeaderProps) {
const leftSidebarOpen = useAtomValue(appSidebarOpenAtom);
const RightHeader = isWindowsDesktop
? WindowsMainPageHeaderRight
: NonWindowsMainPageHeaderRight;
return (
<Header className={styles.mainHeader}>
<SidebarSwitch show={!leftSidebarOpen} />
{!leftSidebarOpen ? <HeaderDivider /> : null}
<BlockSuiteHeaderTitle
pageId={page.id}
<EditorModeSwitch
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
pageId={page?.id}
/>
<div className={styles.journalWeekPicker}>
<JournalWeekDatePicker
workspace={workspace.blockSuiteWorkspace}
page={page}
/>
</div>
<JournalTodayButton workspace={workspace.blockSuiteWorkspace} />
<HeaderDivider />
<PageHeaderMenuButton isJournal pageId={page?.id} />
{page ? (
<SharePageButton isJournal workspace={workspace} page={page} />
) : null}
<RightHeader showSidebarSwitch={showSidebarSwitch} />
</Header>
);
}
export function NormalPageHeader({
page,
workspace,
showSidebarSwitch = true,
}: PageHeaderProps) {
const titleInputHandleRef = useRef<InlineEditHandle>(null);
const leftSidebarOpen = useAtomValue(appSidebarOpenAtom);
const onRename = useCallback(() => {
setTimeout(() => titleInputHandleRef.current?.triggerEdit());
}, []);
return (
<Header className={styles.mainHeader}>
<SidebarSwitch show={!leftSidebarOpen} />
{!leftSidebarOpen ? <HeaderDivider /> : null}
<EditorModeSwitch
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
pageId={page?.id}
/>
<BlocksuiteHeaderTitle
inputHandleRef={titleInputHandleRef}
pageId={page?.id}
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
/>
<PageHeaderMenuButton rename={onRename} pageId={page?.id} />
<FavoriteButton pageId={page?.id} />
<div className={styles.spacer} />
{page ? <SharePageButton workspace={workspace} page={page} /> : null}
<RightHeader showSidebarSwitch={showSidebarSwitch} />
@ -128,6 +175,18 @@ export function DetailPageHeader({
);
}
export function DetailPageHeader(props: PageHeaderProps) {
const { page } = props;
const { isJournal } = useJournalInfoHelper(page.meta);
const isInTrash = page.meta.trash;
return isJournal && !isInTrash ? (
<JournalPageHeader {...props} />
) : (
<NormalPageHeader {...props} />
);
}
function WindowsSidebarHeader() {
return (
<>