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:
JimmFly
2024-09-05 07:14:23 +00:00
parent 03c2051926
commit f452414952
11 changed files with 279 additions and 404 deletions

View File

@@ -1,4 +1,3 @@
import { EditorService } from '@affine/core/modules/editor';
import type { ReferenceInfo } from '@blocksuite/affine-model'; import type { ReferenceInfo } from '@blocksuite/affine-model';
import type { DocMode } from '@blocksuite/blocks'; import type { DocMode } from '@blocksuite/blocks';
import type { import type {
@@ -8,13 +7,12 @@ import type {
PageEditor, PageEditor,
} from '@blocksuite/presets'; } from '@blocksuite/presets';
import { type Doc, Slot } from '@blocksuite/store'; import { type Doc, Slot } from '@blocksuite/store';
import { useService } from '@toeverything/infra';
import clsx from 'clsx'; import clsx from 'clsx';
import type React from 'react'; import type React from 'react';
import { import {
forwardRef, forwardRef,
useCallback, useCallback,
useEffect, useImperativeHandle,
useLayoutEffect, useLayoutEffect,
useMemo, useMemo,
useRef, useRef,
@@ -60,7 +58,6 @@ export const BlocksuiteEditorContainer = forwardRef<
{ page, mode, className, style, shared }, { page, mode, className, style, shared },
ref ref
) { ) {
const editorService = useService(EditorService);
const rootRef = useRef<HTMLDivElement>(null); const rootRef = useRef<HTMLDivElement>(null);
const docRef = useRef<PageEditor>(null); const docRef = useRef<PageEditor>(null);
const docTitleRef = useRef<DocTitle>(null); const docTitleRef = useRef<DocTitle>(null);
@@ -112,6 +109,9 @@ export const BlocksuiteEditorContainer = forwardRef<
get doc() { get doc() {
return page; return page;
}, },
get docTitle() {
return docTitleRef.current;
},
get host() { get host() {
return mode === 'page' return mode === 'page'
? docRef.current?.host ? docRef.current?.host
@@ -159,35 +159,9 @@ export const BlocksuiteEditorContainer = forwardRef<
return proxy; return proxy;
}, [mode, page, slots]); }, [mode, page, slots]);
useEffect(() => { useImperativeHandle(ref, () => affineEditorContainerProxy, [
if (ref) { affineEditorContainerProxy,
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]);
const handleClickPageModeBlank = useCallback(() => { const handleClickPageModeBlank = useCallback(() => {
affineEditorContainerProxy.host?.std.command.exec( affineEditorContainerProxy.host?.std.command.exec(

View File

@@ -1,32 +1,30 @@
import { useRefEffect } from '@affine/component';
import { EditorLoading } from '@affine/component/page-detail-skeleton'; import { EditorLoading } from '@affine/component/page-detail-skeleton';
import type { DocMode } from '@blocksuite/blocks'; import {
import { assertExists } from '@blocksuite/global/utils'; 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 { AffineEditorContainer } from '@blocksuite/presets';
import type { Doc } from '@blocksuite/store'; import type { Doc } from '@blocksuite/store';
import { use } from 'foxact/use'; import { use } from 'foxact/use';
import type { CSSProperties, ReactElement } from 'react'; import type { CSSProperties } from 'react';
import { import { Suspense, useEffect } from 'react';
forwardRef,
memo,
Suspense,
useCallback,
useEffect,
useRef,
} from 'react';
import { BlocksuiteEditorContainer } from './blocksuite-editor-container'; import { BlocksuiteEditorContainer } from './blocksuite-editor-container';
import { NoPageRootError } from './no-page-error'; import { NoPageRootError } from './no-page-error';
export type ErrorBoundaryProps = {
onReset?: () => void;
};
export type EditorProps = { export type EditorProps = {
page: Doc; page: Doc;
mode: DocMode; mode: DocMode;
shared?: boolean; shared?: boolean;
// on Editor instance instantiated // on Editor ready
onLoadEditor?: (editor: AffineEditorContainer) => () => void; onEditorReady?: (editor: AffineEditorContainer) => (() => void) | void;
style?: CSSProperties; style?: CSSProperties;
className?: string; className?: string;
}; };
@@ -53,73 +51,100 @@ function usePageRoot(page: Doc) {
return page.root; return page.root;
} }
const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>( const BlockSuiteEditorImpl = ({
function BlockSuiteEditorImpl( mode,
{ mode, page, className, onLoadEditor, shared, style }, page,
ref className,
) { shared,
usePageRoot(page); style,
assertExists(page, 'page should not be null'); onEditorReady,
const editorDisposeRef = useRef<() => void>(() => {}); }: EditorProps) => {
const editorRef = useRef<AffineEditorContainer | null>(null); usePageRoot(page);
const onRefChange = useCallback( useEffect(() => {
(editor: AffineEditorContainer | null) => { const disposable = page.slots.blockUpdated.once(() => {
editorRef.current = editor; page.collection.setDocMeta(page.id, {
if (ref) { updatedDate: Date.now(),
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(),
});
}); });
});
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 () => { return () => {
disposable.dispose(); canceled = true;
disposableGroup.dispose();
}; };
}, [page]); },
[onEditorReady, page]
);
useEffect(() => { return (
return () => { <BlocksuiteEditorContainer
editorDisposeRef.current(); mode={mode}
}; page={page}
}, []); shared={shared}
ref={editorRef}
className={className}
style={style}
/>
);
};
return ( export const BlockSuiteEditor = (props: EditorProps) => {
<BlocksuiteEditorContainer return (
mode={mode} <Suspense fallback={<EditorLoading />}>
page={page} <BlockSuiteEditorImpl key={props.page.id} {...props} />
shared={shared} </Suspense>
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';

View File

@@ -1,16 +1,16 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { EditorService } from '@affine/core/modules/editor';
import { PresentationIcon } from '@blocksuite/icons/rc'; import { PresentationIcon } from '@blocksuite/icons/rc';
import { useService } from '@toeverything/infra';
import { usePresent } from './use-present';
export const DetailPageHeaderPresentButton = () => { export const DetailPageHeaderPresentButton = () => {
const { isPresent, handlePresent } = usePresent(); const editorService = useService(EditorService);
return ( return (
<IconButton <IconButton
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
size="24" size="24"
onClick={() => handlePresent(!isPresent)} onClick={() => editorService.editor.togglePresentation()}
> >
<PresentationIcon /> <PresentationIcon />
</IconButton> </IconButton>

View File

@@ -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,
};
};

View File

@@ -1,19 +1,21 @@
import { Button } from '@affine/component/ui/button'; import { Button } from '@affine/component/ui/button';
import { EditorService } from '@affine/core/modules/editor';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { PresentationIcon } from '@blocksuite/icons/rc'; 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'; import * as styles from './styles.css';
export const PresentButton = () => { export const PresentButton = () => {
const t = useI18n(); const t = useI18n();
const { isPresent, handlePresent } = usePresent(); const editorService = useService(EditorService);
const isPresent = useLiveData(editorService.editor.isPresenting$);
return ( return (
<Button <Button
prefix={<PresentationIcon />} prefix={<PresentationIcon />}
className={styles.presentButton} className={styles.presentButton}
onClick={() => handlePresent()} onClick={() => editorService.editor.togglePresentation()}
disabled={isPresent} disabled={isPresent}
> >
{t['com.affine.share-page.header.present']()} {t['com.affine.share-page.header.present']()}

View File

@@ -79,9 +79,7 @@ export const PublishPageUserAvatar = () => {
<Menu <Menu
items={menuItem} items={menuItem}
contentOptions={{ contentOptions={{
style: { align: 'end',
transform: 'translateX(-16px)',
},
}} }}
> >
<div className={styles.iconWrapper} data-testid="share-page-user-avatar"> <div className={styles.iconWrapper} data-testid="share-page-user-avatar">

View File

@@ -1,15 +1,11 @@
import './page-detail-editor.css'; 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 { AffineEditorContainer } from '@blocksuite/presets';
import type { Doc as BlockSuiteDoc, DocCollection } from '@blocksuite/store';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import clsx from 'clsx'; import clsx from 'clsx';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { memo, useCallback, useMemo } from 'react'; import { useMemo } from 'react';
import { EditorService } from '../modules/editor'; import { EditorService } from '../modules/editor';
import { import {
@@ -25,22 +21,14 @@ declare global {
} }
export type OnLoadEditor = ( export type OnLoadEditor = (
page: BlockSuiteDoc,
editor: AffineEditorContainer editor: AffineEditorContainer
) => () => void; ) => (() => void) | void;
export interface PageDetailEditorProps { export interface PageDetailEditorProps {
isPublic?: boolean;
publishMode?: DocMode;
docCollection: DocCollection;
pageId: string;
onLoad?: OnLoadEditor; onLoad?: OnLoadEditor;
} }
const PageDetailEditorMain = memo(function PageDetailEditorMain({ export const PageDetailEditor = ({ onLoad }: PageDetailEditorProps) => {
page,
onLoad,
}: PageDetailEditorProps & { page: BlockSuiteDoc }) {
const editor = useService(EditorService).editor; const editor = useService(EditorService).editor;
const mode = useLiveData(editor.mode$); const mode = useLiveData(editor.mode$);
@@ -68,30 +56,6 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
: fontStyle.value; : fontStyle.value;
}, [settings.customFontFamily, settings.fontFamily]); }, [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 ( return (
<Editor <Editor
className={clsx(styles.editor, { className={clsx(styles.editor, {
@@ -104,18 +68,9 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
} as CSSProperties } as CSSProperties
} }
mode={mode} mode={mode}
page={page} page={editor.doc.blockSuiteDoc}
shared={isSharedMode} 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} />;
}; };

View File

@@ -1,4 +1,4 @@
import type { DocMode } from '@blocksuite/blocks'; import type { DocMode, EdgelessRootService } from '@blocksuite/blocks';
import type { InlineEditor } from '@blocksuite/inline/inline-editor'; import type { InlineEditor } from '@blocksuite/inline/inline-editor';
import type { AffineEditorContainer, DocTitle } from '@blocksuite/presets'; import type { AffineEditorContainer, DocTitle } from '@blocksuite/presets';
import type { DocService, WorkspaceService } from '@toeverything/infra'; import type { DocService, WorkspaceService } from '@toeverything/infra';
@@ -23,6 +23,20 @@ export class Editor extends Entity {
readonly editorContainer$ = new LiveData<AffineEditorContainer | null>(null); readonly editorContainer$ = new LiveData<AffineEditorContainer | null>(null);
isPresenting$ = new LiveData<boolean>(false);
togglePresentation() {
const edgelessRootService =
this.editorContainer$.value?.host?.std.getService(
'affine:page'
) as EdgelessRootService;
if (!edgelessRootService) return;
edgelessRootService.tool.setEdgelessTool({
type: !this.isPresenting$.value ? 'frameNavigator' : 'default',
});
}
setSelector(selector: EditorSelector | undefined) { setSelector(selector: EditorSelector | undefined) {
this.selector$.next(selector); this.selector$.next(selector);
} }
@@ -145,8 +159,10 @@ export class Editor extends Entity {
bindEditorContainer( bindEditorContainer(
editorContainer: AffineEditorContainer, editorContainer: AffineEditorContainer,
docTitle: DocTitle docTitle: DocTitle | null
) { ) {
const unsubs: (() => void)[] = [];
const focusAt$ = LiveData.computed(get => { const focusAt$ = LiveData.computed(get => {
const selector = get(this.selector$); const selector = get(this.selector$);
const id = const id =
@@ -159,26 +175,49 @@ export class Editor extends Entity {
return null; return null;
} }
}); });
if (focusAt$.value === null) { if (focusAt$.value === null && docTitle) {
const title = docTitle.querySelector< const title = docTitle.querySelector<
HTMLElement & { inlineEditor: InlineEditor } HTMLElement & { inlineEditor: InlineEditor }
>('rich-text'); >('rich-text');
title?.inlineEditor.focusEnd(); title?.inlineEditor.focusEnd();
} }
const unsubscribe = focusAt$ unsubs.push(
.distinctUntilChanged( focusAt$
(a, b) => a?.id === b?.id && a?.refreshKey === b?.refreshKey .distinctUntilChanged(
) (a, b) => a?.id === b?.id && a?.refreshKey === b?.refreshKey
.subscribe(params => { )
if (params?.id) { .subscribe(params => {
const std = editorContainer.host?.std; if (params?.id) {
if (std) { const std = editorContainer.host?.std;
scrollAnchoring(std, this.mode$.value, params.id); if (std) {
scrollAnchoring(std, this.mode$.value, params.id);
}
} }
} }).unsubscribe
}); );
const edgelessPage = editorContainer.host?.querySelector(
'affine-edgeless-root'
);
if (!edgelessPage) {
this.isPresenting$.next(false);
} else {
this.isPresenting$.next(
edgelessPage.edgelessTool.type === 'frameNavigator'
);
unsubs.push(
edgelessPage.slots.edgelessToolUpdated.on(() => {
this.isPresenting$.next(
edgelessPage.edgelessTool.type === 'frameNavigator'
);
}).dispose
);
}
return () => { return () => {
unsubscribe.unsubscribe(); for (const unsub of unsubs) {
unsub();
}
}; };
} }

View File

@@ -4,7 +4,6 @@ import { AIProvider } from '@affine/core/blocksuite/presets/ai';
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
import { BlockSuiteEditor } from '@affine/core/components/blocksuite/block-suite-editor'; import { BlockSuiteEditor } from '@affine/core/components/blocksuite/block-suite-editor';
import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer'; import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { EditorService } from '@affine/core/modules/editor'; import { EditorService } from '@affine/core/modules/editor';
import { PageNotFound } from '@affine/core/pages/404'; import { PageNotFound } from '@affine/core/pages/404';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
@@ -18,7 +17,7 @@ import {
useServices, useServices,
} from '@toeverything/infra'; } from '@toeverything/infra';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect } from 'react';
import { WorkbenchService } from '../../../workbench'; import { WorkbenchService } from '../../../workbench';
import { PeekViewService } from '../../services/peek-view'; import { PeekViewService } from '../../services/peek-view';
@@ -75,26 +74,48 @@ function DocPeekPreviewEditor({
const doc = editor.doc; const doc = editor.doc;
const workspace = editor.doc.workspace; const workspace = editor.doc.workspace;
const mode = useLiveData(editor.mode$); const mode = useLiveData(editor.mode$);
const { jumpToTag } = useNavigateHelper();
const workbench = useService(WorkbenchService).workbench; const workbench = useService(WorkbenchService).workbench;
const peekView = useService(PeekViewService).peekView; const peekView = useService(PeekViewService).peekView;
const [editorElement, setEditorElement] = const editorElement = useLiveData(editor.editorContainer$);
useState<AffineEditorContainer | null>(null);
const onRef = (editor: AffineEditorContainer) => { const handleOnEditorReady = useCallback(
setEditorElement(editor); (editorContainer: AffineEditorContainer) => {
}; if (!editorContainer.host) {
return;
}
const disposableGroup = new DisposableGroup();
const rootService = editorContainer.host.std.getService('affine:page');
// doc change event inside peek view should be handled by peek view
disposableGroup.add(
rootService.slots.docLinkClicked.on(options => {
peekView
.open({
type: 'doc',
docId: options.pageId,
...options.params,
})
.catch(console.error);
})
);
useEffect(() => { editor.setEditorContainer(editorContainer);
editorElement?.updateComplete const unbind = editor.bindEditorContainer(
.then(() => { editorContainer,
if (mode === 'edgeless') { (editorContainer as any).title
fitViewport(editorElement, xywh); );
}
}) if (mode === 'edgeless') {
.catch(console.error); fitViewport(editorContainer, xywh);
return; }
}, [editorElement, mode, xywh]);
return () => {
unbind();
editor.setEditorContainer(null);
disposableGroup.dispose();
};
},
[editor, mode, peekView, xywh]
);
useEffect(() => { useEffect(() => {
const disposable = AIProvider.slots.requestOpenWithChat.on(() => { const disposable = AIProvider.slots.requestOpenWithChat.on(() => {
@@ -110,43 +131,6 @@ function DocPeekPreviewEditor({
}; };
}, [doc, peekView, workbench, workspace.id]); }, [doc, peekView, workbench, workspace.id]);
useEffect(() => {
const disposableGroup = new DisposableGroup();
if (editorElement) {
editorElement.updateComplete
.then(() => {
if (!editorElement.host) {
return;
}
const rootService = editorElement.host.std.getService('affine:page');
// doc change event inside peek view should be handled by peek view
disposableGroup.add(
rootService.slots.docLinkClicked.on(options => {
peekView
.open({
type: 'doc',
docId: options.pageId,
...options.params,
})
.catch(console.error);
})
);
// TODO(@Peng): no tag peek view yet
disposableGroup.add(
rootService.slots.tagClicked.on(({ tagId }) => {
jumpToTag(workspace.id, tagId);
peekView.close();
})
);
})
.catch(console.error);
}
return () => {
disposableGroup.dispose();
};
}, [editorElement, jumpToTag, peekView, workspace.id]);
const openOutlinePanel = useCallback(() => { const openOutlinePanel = useCallback(() => {
workbench.openDoc(doc.id); workbench.openDoc(doc.id);
workbench.openSidebar(); workbench.openSidebar();
@@ -161,10 +145,10 @@ function DocPeekPreviewEditor({
className={clsx('affine-page-viewport', styles.affineDocViewport)} className={clsx('affine-page-viewport', styles.affineDocViewport)}
> >
<BlockSuiteEditor <BlockSuiteEditor
ref={onRef}
className={styles.editor} className={styles.editor}
mode={mode} mode={mode}
page={doc.blockSuiteDoc} page={doc.blockSuiteDoc}
onEditorReady={handleOnEditorReady}
/> />
<EditorOutlineViewer <EditorOutlineViewer
editor={editorElement} editor={editorElement}

View File

@@ -11,22 +11,12 @@ import { EditorSettingService } from '@affine/core/modules/editor-settting';
import { RecentDocsService } from '@affine/core/modules/quicksearch'; import { RecentDocsService } from '@affine/core/modules/quicksearch';
import { ViewService } from '@affine/core/modules/workbench/services/view'; import { ViewService } from '@affine/core/modules/workbench/services/view';
import type { PageRootService } from '@blocksuite/blocks'; import type { PageRootService } from '@blocksuite/blocks';
import {
BookmarkBlockService,
customImageProxyMiddleware,
EmbedGithubBlockService,
EmbedLoomBlockService,
EmbedYoutubeBlockService,
ImageBlockService,
} from '@blocksuite/blocks';
import { DisposableGroup } from '@blocksuite/global/utils'; import { DisposableGroup } from '@blocksuite/global/utils';
import { AiIcon, FrameIcon, TocIcon, TodayIcon } from '@blocksuite/icons/rc'; import { AiIcon, FrameIcon, TocIcon, TodayIcon } from '@blocksuite/icons/rc';
import { type AffineEditorContainer } from '@blocksuite/presets'; import { type AffineEditorContainer } from '@blocksuite/presets';
import type { Doc as BlockSuiteDoc } from '@blocksuite/store';
import { import {
DocService, DocService,
FrameworkScope, FrameworkScope,
globalBlockSuiteSchema,
GlobalContextService, GlobalContextService,
useLiveData, useLiveData,
useService, useService,
@@ -36,7 +26,6 @@ import {
import clsx from 'clsx'; import clsx from 'clsx';
import { memo, useCallback, useEffect, useRef } from 'react'; import { memo, useCallback, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import type { Map as YMap } from 'yjs';
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary'; import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal'; import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal';
@@ -94,7 +83,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const activeSidebarTab = useLiveData(view.activeSidebarTab$); const activeSidebarTab = useLiveData(view.activeSidebarTab$);
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash)); const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper(); const { openPage, jumpToPageBlock } = useNavigateHelper();
const editorContainer = useLiveData(editor.editorContainer$); const editorContainer = useLiveData(editor.editorContainer$);
const isSideBarOpen = useLiveData(workbench.sidebarOpen$); const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
@@ -171,49 +160,10 @@ const DetailPageImpl = memo(function DetailPageImpl() {
usePageDocumentTitle(title); usePageDocumentTitle(title);
const onLoad = useCallback( const onLoad = useCallback(
(bsPage: BlockSuiteDoc, editorContainer: AffineEditorContainer) => { (editorContainer: AffineEditorContainer) => {
try {
// todo(joooye34): improve the following migration code
const surfaceBlock = bsPage.getBlockByFlavour('affine:surface')[0];
// hotfix for old page
if (
surfaceBlock &&
(surfaceBlock.yBlock.get('prop:elements') as YMap<any>).get(
'type'
) !== '$blocksuite:internal:native$'
) {
globalBlockSuiteSchema.upgradeDoc(
0,
{
'affine:surface': 3,
},
bsPage.spaceDoc
);
}
} catch {}
// blocksuite editor host // blocksuite editor host
const editorHost = editorContainer.host; const editorHost = editorContainer.host;
// provide image proxy endpoint to blocksuite
editorHost?.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
);
// provide page mode and updated date to blocksuite
const pageService = const pageService =
editorHost?.std.getService<PageRootService>('affine:page'); editorHost?.std.getService<PageRootService>('affine:page');
const disposable = new DisposableGroup(); const disposable = new DisposableGroup();
@@ -234,27 +184,21 @@ const DetailPageImpl = memo(function DetailPageImpl() {
return openPage(docCollection.id, pageId); return openPage(docCollection.id, pageId);
}) })
); );
disposable.add(
pageService.slots.tagClicked.on(({ tagId }) => {
jumpToTag(workspace.id, tagId);
})
);
} }
editor.setEditorContainer(editorContainer); editor.setEditorContainer(editorContainer);
const unbind = editor.bindEditorContainer(
editorContainer,
(editorContainer as any).docTitle // set from proxy
);
return () => { return () => {
unbind();
editor.setEditorContainer(null);
disposable.dispose(); disposable.dispose();
}; };
}, },
[ [editor, openPage, docCollection.id, jumpToPageBlock]
editor,
jumpToPageBlock,
docCollection.id,
openPage,
jumpToTag,
workspace.id,
]
); );
const [refCallback, hasScrollTop] = useHasScrollTop(); const [refCallback, hasScrollTop] = useHasScrollTop();
@@ -288,11 +232,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
styles.editorContainer styles.editorContainer
)} )}
> >
<PageDetailEditor <PageDetailEditor onLoad={onLoad} />
pageId={doc.id}
onLoad={onLoad}
docCollection={docCollection}
/>
</Scrollable.Viewport> </Scrollable.Viewport>
<Scrollable.Scrollbar <Scrollable.Scrollbar
className={clsx({ className={clsx({

View File

@@ -6,17 +6,19 @@ import { AppContainer, MainContainer } from '@affine/core/components/workspace';
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor'; import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
import { usePageDocumentTitle } from '@affine/core/hooks/use-global-state'; import { usePageDocumentTitle } from '@affine/core/hooks/use-global-state';
import { AuthService } from '@affine/core/modules/cloud'; import { AuthService } from '@affine/core/modules/cloud';
import { type Editor, EditorsService } from '@affine/core/modules/editor'; import {
type Editor,
EditorService,
EditorsService,
} from '@affine/core/modules/editor';
import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; import { PeekViewManagerModal } from '@affine/core/modules/peek-view';
import { ShareReaderService } from '@affine/core/modules/share-doc'; import { ShareReaderService } from '@affine/core/modules/share-doc';
import { CloudBlobStorage } from '@affine/core/modules/workspace-engine'; import { CloudBlobStorage } from '@affine/core/modules/workspace-engine';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { type DocMode, DocModes } from '@blocksuite/blocks'; import { type DocMode, DocModes } from '@blocksuite/blocks';
import { noop } from '@blocksuite/global/utils';
import { Logo1Icon } from '@blocksuite/icons/rc'; import { Logo1Icon } from '@blocksuite/icons/rc';
import type { AffineEditorContainer } from '@blocksuite/presets'; import type { AffineEditorContainer } from '@blocksuite/presets';
import type { Doc as BlockSuiteDoc } from '@blocksuite/store';
import type { Doc, Workspace } from '@toeverything/infra'; import type { Doc, Workspace } from '@toeverything/infra';
import { import {
DocsService, DocsService,
@@ -105,7 +107,6 @@ const SharePageInner = ({
docBinary: Uint8Array; docBinary: Uint8Array;
publishMode?: DocMode; publishMode?: DocMode;
}) => { }) => {
const t = useI18n();
const workspacesService = useService(WorkspacesService); const workspacesService = useService(WorkspacesService);
const [workspace, setWorkspace] = useState<Workspace | null>(null); const [workspace, setWorkspace] = useState<Workspace | null>(null);
@@ -179,15 +180,24 @@ const SharePageInner = ({
const pageTitle = useLiveData(page?.title$); const pageTitle = useLiveData(page?.title$);
usePageDocumentTitle(pageTitle); usePageDocumentTitle(pageTitle);
const authService = useService(AuthService);
const loginStatus = useLiveData(authService.session.status$);
const onEditorLoad = useCallback( const onEditorLoad = useCallback(
(_: BlockSuiteDoc, editor: AffineEditorContainer) => { (editorContainer: AffineEditorContainer) => {
setActiveBlocksuiteEditor(editor); setActiveBlocksuiteEditor(editorContainer);
return noop; if (!editor) {
return;
}
editor.setEditorContainer(editorContainer);
const unbind = editor.bindEditorContainer(
editorContainer,
(editorContainer as any).docTitle
);
return () => {
unbind();
editor.setEditorContainer(null);
};
}, },
[setActiveBlocksuiteEditor] [editor, setActiveBlocksuiteEditor]
); );
if (!workspace || !page || !editor) { if (!workspace || !page || !editor) {
@@ -214,30 +224,12 @@ const SharePageInner = ({
styles.editorContainer styles.editorContainer
)} )}
> >
<PageDetailEditor <PageDetailEditor onLoad={onEditorLoad} />
isPublic
publishMode={publishMode}
docCollection={page.blockSuiteDoc.collection}
pageId={page.id}
onLoad={onEditorLoad}
/>
{publishMode === 'page' ? <ShareFooter /> : null} {publishMode === 'page' ? <ShareFooter /> : null}
</Scrollable.Viewport> </Scrollable.Viewport>
<Scrollable.Scrollbar /> <Scrollable.Scrollbar />
</Scrollable.Root> </Scrollable.Root>
{loginStatus !== 'authenticated' ? ( <SharePageFooter />
<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>
</div> </div>
</MainContainer> </MainContainer>
@@ -248,3 +240,28 @@ const SharePageInner = ({
</FrameworkScope> </FrameworkScope>
); );
}; };
const SharePageFooter = () => {
const t = useI18n();
const editorService = useService(EditorService);
const isPresent = useLiveData(editorService.editor.isPresenting$);
const authService = useService(AuthService);
const loginStatus = useLiveData(authService.session.status$);
if (isPresent || loginStatus === 'authenticated') {
return null;
}
return (
<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>
);
};