mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
fix(core): hide the footer that blocks the toolbar in shared page (#8091)
close PD-1405 CLOUD-64 https://github.com/user-attachments/assets/f6ed2dfc-d238-41d8-abaf-684193a080ff
This commit is contained in:
+7
-33
@@ -1,4 +1,3 @@
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import type { ReferenceInfo } from '@blocksuite/affine-model';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import type {
|
||||
@@ -8,13 +7,12 @@ import type {
|
||||
PageEditor,
|
||||
} from '@blocksuite/presets';
|
||||
import { type Doc, Slot } from '@blocksuite/store';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import type React from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
@@ -60,7 +58,6 @@ export const BlocksuiteEditorContainer = forwardRef<
|
||||
{ page, mode, className, style, shared },
|
||||
ref
|
||||
) {
|
||||
const editorService = useService(EditorService);
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const docRef = useRef<PageEditor>(null);
|
||||
const docTitleRef = useRef<DocTitle>(null);
|
||||
@@ -112,6 +109,9 @@ export const BlocksuiteEditorContainer = forwardRef<
|
||||
get doc() {
|
||||
return page;
|
||||
},
|
||||
get docTitle() {
|
||||
return docTitleRef.current;
|
||||
},
|
||||
get host() {
|
||||
return mode === 'page'
|
||||
? docRef.current?.host
|
||||
@@ -159,35 +159,9 @@ export const BlocksuiteEditorContainer = forwardRef<
|
||||
return proxy;
|
||||
}, [mode, page, slots]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref) {
|
||||
if (typeof ref === 'function') {
|
||||
ref(affineEditorContainerProxy);
|
||||
} else {
|
||||
ref.current = affineEditorContainerProxy;
|
||||
}
|
||||
}
|
||||
}, [affineEditorContainerProxy, ref]);
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
let unsubscribe: () => void = () => {};
|
||||
|
||||
affineEditorContainerProxy.updateComplete
|
||||
.then(() => {
|
||||
if (!canceled) {
|
||||
unsubscribe = editorService.editor.bindEditorContainer(
|
||||
affineEditorContainerProxy,
|
||||
docTitleRef.current as DocTitle
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
return () => {
|
||||
canceled = true;
|
||||
unsubscribe();
|
||||
};
|
||||
}, [affineEditorContainerProxy, mode, editorService]);
|
||||
useImperativeHandle(ref, () => affineEditorContainerProxy, [
|
||||
affineEditorContainerProxy,
|
||||
]);
|
||||
|
||||
const handleClickPageModeBlank = useCallback(() => {
|
||||
affineEditorContainerProxy.host?.std.command.exec(
|
||||
|
||||
+106
-81
@@ -1,32 +1,30 @@
|
||||
import { useRefEffect } from '@affine/component';
|
||||
import { EditorLoading } from '@affine/component/page-detail-skeleton';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
BookmarkBlockService,
|
||||
customImageProxyMiddleware,
|
||||
type DocMode,
|
||||
EmbedGithubBlockService,
|
||||
EmbedLoomBlockService,
|
||||
EmbedYoutubeBlockService,
|
||||
ImageBlockService,
|
||||
} from '@blocksuite/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
import { use } from 'foxact/use';
|
||||
import type { CSSProperties, ReactElement } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
memo,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
|
||||
import { BlocksuiteEditorContainer } from './blocksuite-editor-container';
|
||||
import { NoPageRootError } from './no-page-error';
|
||||
|
||||
export type ErrorBoundaryProps = {
|
||||
onReset?: () => void;
|
||||
};
|
||||
|
||||
export type EditorProps = {
|
||||
page: Doc;
|
||||
mode: DocMode;
|
||||
shared?: boolean;
|
||||
// on Editor instance instantiated
|
||||
onLoadEditor?: (editor: AffineEditorContainer) => () => void;
|
||||
// on Editor ready
|
||||
onEditorReady?: (editor: AffineEditorContainer) => (() => void) | void;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
};
|
||||
@@ -53,73 +51,100 @@ function usePageRoot(page: Doc) {
|
||||
return page.root;
|
||||
}
|
||||
|
||||
const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>(
|
||||
function BlockSuiteEditorImpl(
|
||||
{ mode, page, className, onLoadEditor, shared, style },
|
||||
ref
|
||||
) {
|
||||
usePageRoot(page);
|
||||
assertExists(page, 'page should not be null');
|
||||
const editorDisposeRef = useRef<() => void>(() => {});
|
||||
const editorRef = useRef<AffineEditorContainer | null>(null);
|
||||
const BlockSuiteEditorImpl = ({
|
||||
mode,
|
||||
page,
|
||||
className,
|
||||
shared,
|
||||
style,
|
||||
onEditorReady,
|
||||
}: EditorProps) => {
|
||||
usePageRoot(page);
|
||||
|
||||
const onRefChange = useCallback(
|
||||
(editor: AffineEditorContainer | null) => {
|
||||
editorRef.current = editor;
|
||||
if (ref) {
|
||||
if (typeof ref === 'function') {
|
||||
ref(editor);
|
||||
} else {
|
||||
ref.current = editor;
|
||||
}
|
||||
}
|
||||
if (editor && onLoadEditor) {
|
||||
editorDisposeRef.current = onLoadEditor(editor);
|
||||
}
|
||||
},
|
||||
[onLoadEditor, ref]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = page.slots.blockUpdated.once(() => {
|
||||
page.collection.setDocMeta(page.id, {
|
||||
updatedDate: Date.now(),
|
||||
});
|
||||
useEffect(() => {
|
||||
const disposable = page.slots.blockUpdated.once(() => {
|
||||
page.collection.setDocMeta(page.id, {
|
||||
updatedDate: Date.now(),
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [page]);
|
||||
|
||||
const editorRef = useRefEffect(
|
||||
(editor: AffineEditorContainer) => {
|
||||
globalThis.currentEditor = editor;
|
||||
let canceled = false;
|
||||
const disposableGroup = new DisposableGroup();
|
||||
|
||||
if (onEditorReady) {
|
||||
// Invoke onLoad once the editor has been mounted to the DOM.
|
||||
editor.updateComplete
|
||||
.then(() => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
// host should be ready
|
||||
|
||||
// provide image proxy endpoint to blocksuite
|
||||
editor.host?.std.clipboard.use(
|
||||
customImageProxyMiddleware(runtimeConfig.imageProxyUrl)
|
||||
);
|
||||
ImageBlockService.setImageProxyURL(runtimeConfig.imageProxyUrl);
|
||||
|
||||
// provide link preview endpoint to blocksuite
|
||||
BookmarkBlockService.setLinkPreviewEndpoint(
|
||||
runtimeConfig.linkPreviewUrl
|
||||
);
|
||||
EmbedGithubBlockService.setLinkPreviewEndpoint(
|
||||
runtimeConfig.linkPreviewUrl
|
||||
);
|
||||
EmbedYoutubeBlockService.setLinkPreviewEndpoint(
|
||||
runtimeConfig.linkPreviewUrl
|
||||
);
|
||||
EmbedLoomBlockService.setLinkPreviewEndpoint(
|
||||
runtimeConfig.linkPreviewUrl
|
||||
);
|
||||
|
||||
return editor.host?.updateComplete;
|
||||
})
|
||||
.then(() => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
const dispose = onEditorReady(editor);
|
||||
if (dispose) {
|
||||
disposableGroup.add(dispose);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
canceled = true;
|
||||
disposableGroup.dispose();
|
||||
};
|
||||
}, [page]);
|
||||
},
|
||||
[onEditorReady, page]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
editorDisposeRef.current();
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<BlocksuiteEditorContainer
|
||||
mode={mode}
|
||||
page={page}
|
||||
shared={shared}
|
||||
ref={editorRef}
|
||||
className={className}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<BlocksuiteEditorContainer
|
||||
mode={mode}
|
||||
page={page}
|
||||
shared={shared}
|
||||
ref={onRefChange}
|
||||
className={className}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const BlockSuiteEditor = memo(
|
||||
forwardRef<AffineEditorContainer, EditorProps>(
|
||||
function BlockSuiteEditor(props, ref): ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<EditorLoading />}>
|
||||
<BlockSuiteEditorImpl key={props.page.id} ref={ref} {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
BlockSuiteEditor.displayName = 'BlockSuiteEditor';
|
||||
export const BlockSuiteEditor = (props: EditorProps) => {
|
||||
return (
|
||||
<Suspense fallback={<EditorLoading />}>
|
||||
<BlockSuiteEditorImpl key={props.page.id} {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -1,16 +1,16 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { PresentationIcon } from '@blocksuite/icons/rc';
|
||||
|
||||
import { usePresent } from './use-present';
|
||||
import { useService } from '@toeverything/infra';
|
||||
|
||||
export const DetailPageHeaderPresentButton = () => {
|
||||
const { isPresent, handlePresent } = usePresent();
|
||||
const editorService = useService(EditorService);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
style={{ flexShrink: 0 }}
|
||||
size="24"
|
||||
onClick={() => handlePresent(!isPresent)}
|
||||
onClick={() => editorService.editor.togglePresentation()}
|
||||
>
|
||||
<PresentationIcon />
|
||||
</IconButton>
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
|
||||
import type { EdgelessRootService } from '@blocksuite/blocks';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export const usePresent = () => {
|
||||
const [isPresent, setIsPresent] = useState(false);
|
||||
const [editor] = useActiveBlocksuiteEditor();
|
||||
|
||||
const handlePresent = useCallback(
|
||||
(enable = true) => {
|
||||
isPresent;
|
||||
const editorHost = editor?.host;
|
||||
if (!editorHost) return;
|
||||
|
||||
// TODO(@catsjuice): use surfaceService subAtom
|
||||
const enterOrLeavePresentationMode = () => {
|
||||
const edgelessRootService = editorHost.std.getService(
|
||||
'affine:page'
|
||||
) as EdgelessRootService;
|
||||
|
||||
if (!edgelessRootService) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeTool = edgelessRootService.tool.edgelessTool.type;
|
||||
const isFrameNavigator = activeTool === 'frameNavigator';
|
||||
if ((enable && isFrameNavigator) || (!enable && !isFrameNavigator))
|
||||
return;
|
||||
|
||||
edgelessRootService.tool.setEdgelessTool({
|
||||
type: enable ? 'frameNavigator' : 'default',
|
||||
});
|
||||
};
|
||||
|
||||
enterOrLeavePresentationMode();
|
||||
setIsPresent(enable);
|
||||
},
|
||||
[editor?.host, isPresent]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPresent) return;
|
||||
|
||||
const editorHost = editor?.host;
|
||||
if (!editorHost) return;
|
||||
|
||||
const edgelessPage = editorHost?.querySelector('affine-edgeless-root');
|
||||
if (!edgelessPage) return;
|
||||
|
||||
return edgelessPage.slots.edgelessToolUpdated.on(() => {
|
||||
setIsPresent(edgelessPage.edgelessTool.type === 'frameNavigator');
|
||||
}).dispose;
|
||||
}, [editor?.host, isPresent]);
|
||||
|
||||
return {
|
||||
isPresent,
|
||||
handlePresent,
|
||||
};
|
||||
};
|
||||
@@ -1,19 +1,21 @@
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { PresentationIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
|
||||
import { usePresent } from '../../blocksuite/block-suite-header/present/use-present';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export const PresentButton = () => {
|
||||
const t = useI18n();
|
||||
const { isPresent, handlePresent } = usePresent();
|
||||
const editorService = useService(EditorService);
|
||||
const isPresent = useLiveData(editorService.editor.isPresenting$);
|
||||
|
||||
return (
|
||||
<Button
|
||||
prefix={<PresentationIcon />}
|
||||
className={styles.presentButton}
|
||||
onClick={() => handlePresent()}
|
||||
onClick={() => editorService.editor.togglePresentation()}
|
||||
disabled={isPresent}
|
||||
>
|
||||
{t['com.affine.share-page.header.present']()}
|
||||
|
||||
@@ -79,9 +79,7 @@ export const PublishPageUserAvatar = () => {
|
||||
<Menu
|
||||
items={menuItem}
|
||||
contentOptions={{
|
||||
style: {
|
||||
transform: 'translateX(-16px)',
|
||||
},
|
||||
align: 'end',
|
||||
}}
|
||||
>
|
||||
<div className={styles.iconWrapper} data-testid="share-page-user-avatar">
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import './page-detail-editor.css';
|
||||
|
||||
import { useDocCollectionPage } from '@affine/core/hooks/use-block-suite-workspace-page';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Doc as BlockSuiteDoc, DocCollection } from '@blocksuite/store';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import clsx from 'clsx';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { EditorService } from '../modules/editor';
|
||||
import {
|
||||
@@ -25,22 +21,14 @@ declare global {
|
||||
}
|
||||
|
||||
export type OnLoadEditor = (
|
||||
page: BlockSuiteDoc,
|
||||
editor: AffineEditorContainer
|
||||
) => () => void;
|
||||
) => (() => void) | void;
|
||||
|
||||
export interface PageDetailEditorProps {
|
||||
isPublic?: boolean;
|
||||
publishMode?: DocMode;
|
||||
docCollection: DocCollection;
|
||||
pageId: string;
|
||||
onLoad?: OnLoadEditor;
|
||||
}
|
||||
|
||||
const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
||||
page,
|
||||
onLoad,
|
||||
}: PageDetailEditorProps & { page: BlockSuiteDoc }) {
|
||||
export const PageDetailEditor = ({ onLoad }: PageDetailEditorProps) => {
|
||||
const editor = useService(EditorService).editor;
|
||||
const mode = useLiveData(editor.mode$);
|
||||
|
||||
@@ -68,30 +56,6 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
||||
: fontStyle.value;
|
||||
}, [settings.customFontFamily, settings.fontFamily]);
|
||||
|
||||
const onLoadEditor = useCallback(
|
||||
(editor: AffineEditorContainer) => {
|
||||
// debug current detail editor
|
||||
globalThis.currentEditor = editor;
|
||||
const disposableGroup = new DisposableGroup();
|
||||
localStorage.setItem('last_page_id', page.id);
|
||||
|
||||
if (onLoad) {
|
||||
// Invoke onLoad once the editor has been mounted to the DOM.
|
||||
editor.updateComplete
|
||||
.then(() => editor.host?.updateComplete)
|
||||
.then(() => {
|
||||
disposableGroup.add(onLoad(page, editor));
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
return () => {
|
||||
disposableGroup.dispose();
|
||||
};
|
||||
},
|
||||
[onLoad, page]
|
||||
);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
className={clsx(styles.editor, {
|
||||
@@ -104,18 +68,9 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
||||
} as CSSProperties
|
||||
}
|
||||
mode={mode}
|
||||
page={page}
|
||||
page={editor.doc.blockSuiteDoc}
|
||||
shared={isSharedMode}
|
||||
onLoadEditor={onLoadEditor}
|
||||
onEditorReady={onLoad}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const PageDetailEditor = (props: PageDetailEditorProps) => {
|
||||
const { docCollection, pageId } = props;
|
||||
const page = useDocCollectionPage(docCollection, pageId);
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
return <PageDetailEditorMain {...props} page={page} />;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user