refactor(core): use custom scrollbar for editor and adjust shared page style (#5752)

Close TOV-481
- Use a custom scrollbar component for editor
- Modified the header of the share page and added a new footer
This commit is contained in:
JimmFly
2024-02-26 08:54:52 +00:00
parent e9f9eea80c
commit 0be62d892d
26 changed files with 504 additions and 209 deletions

View File

@@ -139,11 +139,16 @@ const HistoryEditorPreview = ({
{snapshotPage ? (
<AffineErrorBoundary>
<BlockSuiteEditor
className={styles.editor}
mode={mode}
page={snapshotPage}
/>
<Scrollable.Root>
<Scrollable.Viewport>
<BlockSuiteEditor
className={styles.editor}
mode={mode}
page={snapshotPage}
/>
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
</AffineErrorBoundary>
) : (
<div className={styles.loadingContainer}>

View File

@@ -95,7 +95,6 @@ export const previewHeaderTimestamp = style({
export const editor = style({
height: '100%',
flexGrow: 1,
overflow: 'hidden',
});
export const rowWrapper = style({
display: 'flex',

View File

@@ -104,7 +104,10 @@ export const BlocksuiteDocEditor = forwardRef<
return (
<div className={styles.docEditorRoot}>
<div className={clsx('affine-doc-viewport', styles.affineDocViewport)}>
<div
className={clsx('affine-doc-viewport', styles.affineDocViewport)}
data-doc-viewport={true}
>
{!isJournal ? (
<adapted.DocTitle page={page} ref={titleRef} />
) : (

View File

@@ -2,8 +2,6 @@ import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const docEditorRoot = style({
display: 'block',
height: '100%',
overflow: 'hidden',
background: cssVar('backgroundPrimaryColor'),
});
@@ -11,9 +9,6 @@ export const docEditorRoot = style({
export const affineDocViewport = style({
display: 'flex',
flexDirection: 'column',
height: '100%',
overflowX: 'hidden',
overflowY: 'auto',
userSelect: 'none',
containerName: 'viewport',
// todo: find out what this does in bs
@@ -25,6 +20,13 @@ export const affineDocViewport = style({
zIndex: -1,
},
},
selectors: {
'&[data-doc-viewport="true"]': {
overflowX: 'visible',
overflowY: 'visible',
height: 'auto',
},
},
});
export const docContainer = style({

View File

@@ -1,26 +0,0 @@
import { Logo1Icon } from '@blocksuite/icons';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
import * as styles from './styles.css';
import { PublishPageUserAvatar } from './user-avatar';
const ShareHeaderLeftItem = () => {
const loginStatus = useCurrentLoginStatus();
if (loginStatus === 'authenticated') {
return <PublishPageUserAvatar />;
}
return (
<a
href="https://affine.pro/"
target="_blank"
rel="noreferrer"
className={styles.iconWrapper}
data-testid="share-page-logo"
>
<Logo1Icon />
</a>
);
};
export default ShareHeaderLeftItem;

View File

@@ -1,15 +0,0 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const iconWrapper = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '24px',
cursor: 'pointer',
color: cssVar('textPrimaryColor'),
selectors: {
'&:visited': {
color: cssVar('textPrimaryColor'),
},
},
});

View File

@@ -1,45 +0,0 @@
import { Avatar } from '@affine/component/ui/avatar';
import { Menu, MenuIcon, MenuItem } from '@affine/component/ui/menu';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { SignOutIcon } from '@blocksuite/icons';
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { useCurrentUser } from '../../../hooks/affine/use-current-user';
import { signOutCloud } from '../../../utils/cloud-utils';
import * as styles from './styles.css';
export const PublishPageUserAvatar = () => {
const user = useCurrentUser();
const t = useAFFiNEI18N();
const location = useLocation();
const handleSignOut = useAsyncCallback(async () => {
await signOutCloud({ callbackUrl: location.pathname });
}, [location.pathname]);
const menuItem = useMemo(() => {
return (
<MenuItem
preFix={
<MenuIcon>
<SignOutIcon />
</MenuIcon>
}
data-testid="share-page-sign-out-option"
onClick={handleSignOut}
>
{t['com.affine.workspace.cloud.account.logout']()}
</MenuItem>
);
}, [handleSignOut, t]);
return (
<Menu items={menuItem}>
<div className={styles.iconWrapper} data-testid="share-page-user-avatar">
<Avatar size={24} url={user.image} name={user.name} />
</div>
</Menu>
);
};

View File

@@ -1,12 +1,17 @@
import { Button } from '@affine/component/ui/button';
import { useCurrentUser } from '@affine/core/hooks/affine/use-current-user';
import { useMembers } from '@affine/core/hooks/affine/use-members';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useCallback, useEffect } from 'react';
import { useCurrentUser } from '../../../hooks/affine/use-current-user';
import { useMembers } from '../../../hooks/affine/use-members';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import type { ShareHeaderRightItemProps } from './index';
import * as styles from './styles.css';
export const AuthenticatedItem = ({ ...props }: ShareHeaderRightItemProps) => {
export const AuthenticatedItem = ({
setIsMember,
...props
}: { setIsMember: (value: boolean) => void } & ShareHeaderRightItemProps) => {
const { workspaceId, pageId } = props;
const user = useCurrentUser();
const members = useMembers(workspaceId, 0);
@@ -14,11 +19,21 @@ export const AuthenticatedItem = ({ ...props }: ShareHeaderRightItemProps) => {
const t = useAFFiNEI18N();
const { jumpToPage } = useNavigateHelper();
const handleEdit = useCallback(() => {
jumpToPage(workspaceId, pageId);
}, [workspaceId, pageId, jumpToPage]);
useEffect(() => {
if (isMember) {
setIsMember(true);
}
}, [isMember, setIsMember]);
if (isMember) {
return (
<Button
type="plain"
onClick={() => jumpToPage(workspaceId, pageId)}
className={styles.editButton}
onClick={handleEdit}
data-testid="share-page-edit-button"
>
{t['Edit']()}

View File

@@ -1,8 +1,11 @@
import type { PageMode } from '../../../atoms';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
import type { PageMode } from '@affine/core/atoms';
import { useCurrentLoginStatus } from '@affine/core/hooks/affine/use-current-login-status';
import { useState } from 'react';
import { AuthenticatedItem } from './authenticated-item';
import { PresentButton } from './present';
import * as styles from './styles.css';
import { PublishPageUserAvatar } from './user-avatar';
export type ShareHeaderRightItemProps = {
workspaceId: string;
@@ -13,14 +16,25 @@ export type ShareHeaderRightItemProps = {
const ShareHeaderRightItem = ({ ...props }: ShareHeaderRightItemProps) => {
const loginStatus = useCurrentLoginStatus();
const { publishMode } = props;
const [isMember, setIsMember] = useState(false);
// TODO: Add TOC
return (
<div className={styles.rightItemContainer}>
{loginStatus === 'authenticated' ? (
<AuthenticatedItem {...props} />
<AuthenticatedItem setIsMember={setIsMember} {...props} />
) : null}
{publishMode === 'edgeless' ? <PresentButton /> : null}
{loginStatus === 'authenticated' ? (
<>
<div
className={styles.headerDivider}
data-is-member={isMember}
data-is-edgeless={publishMode === 'edgeless'}
/>
<PublishPageUserAvatar />
</>
) : null}
</div>
);
};

View File

@@ -1,4 +1,5 @@
import { Button } from '@affine/component/ui/button';
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { EdgelessPageService } from '@blocksuite/blocks';
import { PresentationIcon } from '@blocksuite/icons';
@@ -9,15 +10,15 @@ import * as styles from './styles.css';
export const PresentButton = () => {
const t = useAFFiNEI18N();
const [isPresent, setIsPresent] = useState(false);
const [editor] = useActiveBlocksuiteEditor();
const handlePresent = useCallback(() => {
// TODO: use editor Atom
const editorRoot = document.querySelector('editor-host');
if (!editorRoot || isPresent) return;
const editorHost = editor?.host;
if (!editorHost || isPresent) return;
// TODO: use surfaceService subAtom
const enterPresentationMode = () => {
const edgelessPageService = editorRoot?.spec.getService(
const edgelessPageService = editorHost.spec.getService(
'affine:page'
) as EdgelessPageService;
@@ -33,16 +34,15 @@ export const PresentButton = () => {
enterPresentationMode();
setIsPresent(true);
}, [isPresent]);
}, [editor?.host, isPresent]);
useEffect(() => {
if (!isPresent) return;
// TODO: use editor Atom
const editorRoot = document.querySelector('editor-host');
if (!editorRoot) return;
const editorHost = editor?.host;
if (!editorHost) return;
const edgelessPage = editorRoot?.querySelector('affine-edgeless-page');
const edgelessPage = editorHost?.querySelector('affine-edgeless-page');
if (!edgelessPage) return;
edgelessPage.slots.edgelessToolUpdated.on(() => {
@@ -52,15 +52,15 @@ export const PresentButton = () => {
return () => {
edgelessPage.slots.edgelessToolUpdated.dispose();
};
}, [isPresent]);
}, [editor?.host, isPresent]);
return (
<Button
type="primary"
icon={<PresentationIcon />}
className={styles.presentButton}
onClick={handlePresent}
disabled={isPresent}
withoutHoverStyle
>
{t['com.affine.share-page.header.present']()}
</Button>

View File

@@ -1,5 +1,6 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
import { globalStyle, style } from '@vanilla-extract/css';
export const iconWrapper = style({
display: 'flex',
justifyContent: 'center',
@@ -16,9 +17,117 @@ export const iconWrapper = style({
export const rightItemContainer = style({
display: 'flex',
alignItems: 'center',
gap: '8px',
gap: '16px',
padding: '0 8px',
});
export const headerDivider = style({
width: '1px',
height: '20px',
background: cssVar('borderColor'),
display: 'none',
selectors: {
'&[data-is-member="true"]': {
display: 'block',
},
'&[data-is-edgeless="true"]': {
display: 'block',
},
},
'@media': {
'screen and (max-width: 640px)': {
selectors: {
'&[data-is-member="false"][data-is-edgeless="true"]': {
display: 'none',
},
},
},
},
});
export const presentButton = style({
gap: '4px',
background: cssVar('black'),
color: cssVar('white'),
borderColor: cssVar('pureBlack10'),
boxShadow: cssVar('buttonInnerShadow'),
'@media': {
'screen and (max-width: 640px)': {
display: 'none',
},
},
});
globalStyle(`${presentButton} svg`, {
color: cssVar('white'),
});
export const editButton = style({
padding: '4px 8px',
});
export const userPlanButton = style({
display: 'flex',
fontSize: cssVar('fontXs'),
height: 20,
fontWeight: 500,
color: cssVar('pureWhite'),
backgroundColor: cssVar('brandColor'),
padding: '0 4px',
borderRadius: 4,
justifyContent: 'center',
alignItems: 'center',
});
export const accountCard = style({
padding: '4px 8px',
borderRadius: '8px',
userSelect: 'none',
display: 'flex',
columnGap: '10px',
justifyContent: 'space-between',
alignItems: 'center',
});
export const avatar = style({
width: '28px',
height: '28px',
borderRadius: '50%',
fontSize: '20px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexShrink: 0,
});
export const content = style({
flexGrow: '1',
minWidth: 0,
});
export const nameContainer = style({
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
width: '100%',
gap: '4px',
height: '22px',
});
export const userName = style({
fontSize: cssVar('fontSm'),
fontWeight: 600,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
height: '22px',
});
export const userEmail = style({
fontSize: cssVar('fontXs'),
color: cssVar('textSecondaryColor'),
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flexGrow: 1,
height: '20px',
});

View File

@@ -0,0 +1,91 @@
import { Avatar } from '@affine/component/ui/avatar';
import {
Menu,
MenuIcon,
MenuItem,
MenuSeparator,
} from '@affine/component/ui/menu';
import { useCurrentUser } from '@affine/core/hooks/affine/use-current-user';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useUserSubscription } from '@affine/core/hooks/use-subscription';
import { signOutCloud } from '@affine/core/utils/cloud-utils';
import { SubscriptionPlan } from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { SignOutIcon } from '@blocksuite/icons';
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import * as styles from './styles.css';
const UserInfo = () => {
const user = useCurrentUser();
const [subscription] = useUserSubscription();
const plan = subscription?.plan ?? SubscriptionPlan.Free;
return (
<div className={styles.accountCard}>
<Avatar
size={28}
name={user.name}
url={user.image}
className={styles.avatar}
/>
<div className={styles.content}>
<div className={styles.nameContainer}>
<div className={styles.userName} title={user.name}>
{user.name}
</div>
<div className={styles.userPlanButton}>{plan}</div>
</div>
<div className={styles.userEmail} title={user.email}>
{user.email}
</div>
</div>
</div>
);
};
export const PublishPageUserAvatar = () => {
const user = useCurrentUser();
const t = useAFFiNEI18N();
const location = useLocation();
const handleSignOut = useAsyncCallback(async () => {
await signOutCloud({ callbackUrl: location.pathname });
}, [location.pathname]);
const menuItem = useMemo(() => {
return (
<>
<UserInfo />
<MenuSeparator />
<MenuItem
preFix={
<MenuIcon>
<SignOutIcon />
</MenuIcon>
}
data-testid="share-page-sign-out-option"
onClick={handleSignOut}
>
{t['com.affine.workspace.cloud.account.logout']()}
</MenuItem>
</>
);
}, [handleSignOut, t]);
return (
<Menu
items={menuItem}
contentOptions={{
style: {
transform: 'translateX(-16px)',
},
}}
>
<div className={styles.iconWrapper} data-testid="share-page-user-avatar">
<Avatar size={24} url={user.image} name={user.name} />
</div>
</Menu>
);
};

View File

@@ -1,7 +1,6 @@
import { globalStyle, style } from '@vanilla-extract/css';
export const editor = style({
flex: 1,
overflow: 'auto',
selectors: {
'&.full-screen': {
vars: {
@@ -9,16 +8,11 @@ export const editor = style({
'--affine-editor-side-padding': '15px',
},
},
'&.is-public-page': {
height: '100%',
},
},
});
globalStyle(
`${editor} .affine-doc-viewport:not(.affine-embed-synced-doc-editor)`,
{
paddingBottom: '150px',
paddingLeft: '20px',
scrollbarGutter: 'stable',
}
);

View File

@@ -108,7 +108,6 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
<Editor
className={clsx(styles.editor, {
'full-screen': appSettings.fullWidthLayout,
'is-public-page': isPublic,
})}
style={
{

View File

@@ -0,0 +1,58 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const root = style({
display: 'flex',
height: '100%',
overflow: 'hidden',
width: '100%',
});
export const mainContainer = style({
display: 'flex',
flex: 1,
height: '100%',
position: 'relative',
flexDirection: 'column',
minWidth: 0,
overflow: 'hidden',
background: cssVar('backgroundPrimaryColor'),
});
export const editorContainer = style({
position: 'relative',
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
zIndex: 0,
});
export const link = style({
position: 'absolute',
right: '50%',
transform: 'translateX(50%)',
bottom: '20px',
zIndex: cssVar('zIndexPopover'),
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
background: cssVar('black'),
borderRadius: '8px',
border: `1px solid ${cssVar('pureBlack10')}`,
boxShadow: cssVar('--affine-button-inner-shadow'),
color: cssVar('white'),
padding: '8px 18px',
gap: '4px',
'@media': {
print: {
display: 'none',
},
},
});
export const linkText = style({
padding: '0px 4px',
fontSize: cssVar('fontBase'),
fontWeight: 700,
whiteSpace: 'nowrap',
});

View File

@@ -1,11 +1,15 @@
import { Scrollable } from '@affine/component';
import { MainContainer } from '@affine/component/workspace';
import { useCurrentLoginStatus } from '@affine/core/hooks/affine/use-current-login-status';
import { usePageDocumentTitle } from '@affine/core/hooks/use-global-state';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { fetchWithTraceReport } from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
AffineCloudBlobStorage,
StaticBlobStorage,
} from '@affine/workspace-impl';
import { Logo1Icon } from '@blocksuite/icons';
import {
EmptyBlobStorage,
LocalBlobStorage,
@@ -36,6 +40,8 @@ import { PageDetailEditor } from '../../components/page-detail-editor';
import { SharePageNotFoundError } from '../../components/share-page-not-found-error';
import { CurrentPageService } from '../../modules/page';
import { CurrentWorkspaceService } from '../../modules/workspace';
import * as styles from './share-detail-page.css';
import { ShareFooter } from './share-footer';
import { ShareHeader } from './share-header';
type DocPublishMode = 'edgeless' | 'page';
@@ -123,6 +129,7 @@ export const Component = () => {
const workspaceManager = useService(WorkspaceManager);
const currentWorkspace = useService(CurrentWorkspaceService);
const t = useAFFiNEI18N();
useEffect(() => {
// create a workspace for share page
@@ -181,7 +188,7 @@ export const Component = () => {
const page = useServiceOptional(Page);
usePageDocumentTitle(page?.meta);
const loginStatus = useCurrentLoginStatus();
if (!page) {
return;
}
@@ -189,18 +196,41 @@ export const Component = () => {
return (
<AppContainer>
<MainContainer>
<ShareHeader
pageId={page.id}
publishMode={publishMode}
blockSuiteWorkspace={page.blockSuitePage.workspace}
/>
<PageDetailEditor
isPublic
publishMode={publishMode}
workspace={page.blockSuitePage.workspace}
pageId={page.id}
onLoad={() => noop}
/>
<div className={styles.root}>
<div className={styles.mainContainer}>
<ShareHeader
pageId={page.id}
publishMode={publishMode}
blockSuiteWorkspace={page.blockSuitePage.workspace}
/>
<Scrollable.Root>
<Scrollable.Viewport className={styles.editorContainer}>
<PageDetailEditor
isPublic
publishMode={publishMode}
workspace={page.blockSuitePage.workspace}
pageId={page.id}
onLoad={() => noop}
/>
{publishMode === 'page' ? <ShareFooter /> : null}
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
{loginStatus !== 'authenticated' ? (
<a
href="https://affine.pro"
target="_blank"
className={styles.link}
rel="noreferrer"
>
<span className={styles.linkText}>
{t['com.affine.share-page.footer.built-with']()}
</span>
<Logo1Icon fontSize={20} />
</a>
) : null}
</div>
</div>
</MainContainer>
</AppContainer>
);

View File

@@ -0,0 +1,47 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const footerContainer = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1,
width: '100%',
maxWidth: cssVar('editorWidth'),
marginLeft: 'auto',
marginRight: 'auto',
paddingLeft: cssVar('editorSidePadding'),
paddingRight: cssVar('editorSidePadding'),
marginBottom: '200px',
});
export const footer = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
width: '100%',
padding: '12px',
background: cssVar('backgroundOverlayPanelColor'),
});
export const description = style({
fontSize: cssVar('fontSm'),
color: cssVar('textSecondaryColor'),
textAlign: 'center',
});
export const getStartLink = style({
display: 'flex',
padding: '0px 4px',
justifyContent: 'center',
alignItems: 'center',
gap: '4px',
color: cssVar('black'),
selectors: {
'&:visited': {
color: cssVar('black'),
},
},
});

View File

@@ -0,0 +1,26 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightBigIcon } from '@blocksuite/icons';
import * as styles from './share-footer.css';
export const ShareFooter = () => {
const t = useAFFiNEI18N();
return (
<div className={styles.footerContainer}>
<div className={styles.footer}>
<div className={styles.description}>
{t['com.affine.share-page.footer.description']()}
</div>
<a
className={styles.getStartLink}
href="https://affine.pro/"
target="_blank"
rel="noreferrer"
>
{t['com.affine.share-page.footer.get-started']()}
<ArrowRightBigIcon fontSize={16} />
</a>
</div>
</div>
);
};

View File

@@ -0,0 +1,18 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const header = style({
display: 'flex',
height: '52px',
width: '100%',
alignItems: 'center',
flexShrink: 0,
background: cssVar('backgroundPrimaryColor'),
borderBottom: `1px solid ${cssVar('borderColor')}`,
padding: '0 16px',
});
export const spacer = style({
flexGrow: 1,
minWidth: 12,
});

View File

@@ -1,11 +1,10 @@
import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite-mode-switch';
import ShareHeaderRightItem from '@affine/core/components/cloud/share-header-right-item';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { PageMode } from '../../atoms';
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';
import * as styles from './share-header.css';
export function ShareHeader({
pageId,
@@ -17,32 +16,24 @@ export function ShareHeader({
blockSuiteWorkspace: BlockSuiteWorkspace;
}) {
return (
<Header
isFloat={publishMode === 'edgeless'}
left={<ShareHeaderLeftItem />}
center={
<>
<EditorModeSwitch
isPublic
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
publicMode={publishMode}
/>
<BlocksuiteHeaderTitle
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
isPublic={true}
/>
</>
}
right={
<ShareHeaderRightItem
workspaceId={blockSuiteWorkspace.id}
pageId={pageId}
publishMode={publishMode}
/>
}
bottomBorder
/>
<div className={styles.header}>
<EditorModeSwitch
isPublic
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
publicMode={publishMode}
/>
<BlocksuiteHeaderTitle
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
isPublic={true}
/>
<div className={styles.spacer} />
<ShareHeaderRightItem
workspaceId={blockSuiteWorkspace.id}
pageId={pageId}
publishMode={publishMode}
/>
</div>
);
}

View File

@@ -26,7 +26,6 @@ export const editorContainer = style({
display: 'flex',
flexDirection: 'column',
flex: 1,
overflow: 'hidden',
zIndex: 0,
});
export const sidebarContainer = style({

View File

@@ -1,3 +1,4 @@
import { Scrollable } from '@affine/component';
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
import { ResizePanel } from '@affine/component/resize-panel';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
@@ -230,14 +231,17 @@ const DetailPageImpl = memo(function DetailPageImpl() {
main={
// Add a key to force rerender when page changed, to avoid error boundary persisting.
<AffineErrorBoundary key={currentPageId}>
<div className={styles.editorContainer}>
<PageDetailEditor
pageId={currentPageId}
onLoad={onLoad}
workspace={blockSuiteWorkspace}
/>
<HubIsland />
</div>
<Scrollable.Root>
<Scrollable.Viewport className={styles.editorContainer}>
<PageDetailEditor
pageId={currentPageId}
onLoad={onLoad}
workspace={blockSuiteWorkspace}
/>
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
<HubIsland />
</AffineErrorBoundary>
}
footer={isInTrash ? <TrashPageFooter pageId={page.id} /> : null}