mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00: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:
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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 { AffineEditorContainer, DocTitle } from '@blocksuite/presets';
|
||||
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
||||
@@ -23,6 +23,20 @@ export class Editor extends Entity {
|
||||
|
||||
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) {
|
||||
this.selector$.next(selector);
|
||||
}
|
||||
@@ -145,8 +159,10 @@ export class Editor extends Entity {
|
||||
|
||||
bindEditorContainer(
|
||||
editorContainer: AffineEditorContainer,
|
||||
docTitle: DocTitle
|
||||
docTitle: DocTitle | null
|
||||
) {
|
||||
const unsubs: (() => void)[] = [];
|
||||
|
||||
const focusAt$ = LiveData.computed(get => {
|
||||
const selector = get(this.selector$);
|
||||
const id =
|
||||
@@ -159,26 +175,49 @@ export class Editor extends Entity {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (focusAt$.value === null) {
|
||||
if (focusAt$.value === null && docTitle) {
|
||||
const title = docTitle.querySelector<
|
||||
HTMLElement & { inlineEditor: InlineEditor }
|
||||
>('rich-text');
|
||||
title?.inlineEditor.focusEnd();
|
||||
}
|
||||
const unsubscribe = focusAt$
|
||||
.distinctUntilChanged(
|
||||
(a, b) => a?.id === b?.id && a?.refreshKey === b?.refreshKey
|
||||
)
|
||||
.subscribe(params => {
|
||||
if (params?.id) {
|
||||
const std = editorContainer.host?.std;
|
||||
if (std) {
|
||||
scrollAnchoring(std, this.mode$.value, params.id);
|
||||
unsubs.push(
|
||||
focusAt$
|
||||
.distinctUntilChanged(
|
||||
(a, b) => a?.id === b?.id && a?.refreshKey === b?.refreshKey
|
||||
)
|
||||
.subscribe(params => {
|
||||
if (params?.id) {
|
||||
const std = editorContainer.host?.std;
|
||||
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 () => {
|
||||
unsubscribe.unsubscribe();
|
||||
for (const unsub of unsubs) {
|
||||
unsub();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import { BlockSuiteEditor } from '@affine/core/components/blocksuite/block-suite-editor';
|
||||
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 { PageNotFound } from '@affine/core/pages/404';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
@@ -18,7 +17,7 @@ import {
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { WorkbenchService } from '../../../workbench';
|
||||
import { PeekViewService } from '../../services/peek-view';
|
||||
@@ -75,26 +74,48 @@ function DocPeekPreviewEditor({
|
||||
const doc = editor.doc;
|
||||
const workspace = editor.doc.workspace;
|
||||
const mode = useLiveData(editor.mode$);
|
||||
const { jumpToTag } = useNavigateHelper();
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const peekView = useService(PeekViewService).peekView;
|
||||
const [editorElement, setEditorElement] =
|
||||
useState<AffineEditorContainer | null>(null);
|
||||
const editorElement = useLiveData(editor.editorContainer$);
|
||||
|
||||
const onRef = (editor: AffineEditorContainer) => {
|
||||
setEditorElement(editor);
|
||||
};
|
||||
const handleOnEditorReady = useCallback(
|
||||
(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(() => {
|
||||
editorElement?.updateComplete
|
||||
.then(() => {
|
||||
if (mode === 'edgeless') {
|
||||
fitViewport(editorElement, xywh);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
return;
|
||||
}, [editorElement, mode, xywh]);
|
||||
editor.setEditorContainer(editorContainer);
|
||||
const unbind = editor.bindEditorContainer(
|
||||
editorContainer,
|
||||
(editorContainer as any).title
|
||||
);
|
||||
|
||||
if (mode === 'edgeless') {
|
||||
fitViewport(editorContainer, xywh);
|
||||
}
|
||||
|
||||
return () => {
|
||||
unbind();
|
||||
editor.setEditorContainer(null);
|
||||
disposableGroup.dispose();
|
||||
};
|
||||
},
|
||||
[editor, mode, peekView, xywh]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = AIProvider.slots.requestOpenWithChat.on(() => {
|
||||
@@ -110,43 +131,6 @@ function DocPeekPreviewEditor({
|
||||
};
|
||||
}, [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(() => {
|
||||
workbench.openDoc(doc.id);
|
||||
workbench.openSidebar();
|
||||
@@ -161,10 +145,10 @@ function DocPeekPreviewEditor({
|
||||
className={clsx('affine-page-viewport', styles.affineDocViewport)}
|
||||
>
|
||||
<BlockSuiteEditor
|
||||
ref={onRef}
|
||||
className={styles.editor}
|
||||
mode={mode}
|
||||
page={doc.blockSuiteDoc}
|
||||
onEditorReady={handleOnEditorReady}
|
||||
/>
|
||||
<EditorOutlineViewer
|
||||
editor={editorElement}
|
||||
|
||||
@@ -11,22 +11,12 @@ import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||
import { RecentDocsService } from '@affine/core/modules/quicksearch';
|
||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||
import type { PageRootService } from '@blocksuite/blocks';
|
||||
import {
|
||||
BookmarkBlockService,
|
||||
customImageProxyMiddleware,
|
||||
EmbedGithubBlockService,
|
||||
EmbedLoomBlockService,
|
||||
EmbedYoutubeBlockService,
|
||||
ImageBlockService,
|
||||
} from '@blocksuite/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import { AiIcon, FrameIcon, TocIcon, TodayIcon } from '@blocksuite/icons/rc';
|
||||
import { type AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Doc as BlockSuiteDoc } from '@blocksuite/store';
|
||||
import {
|
||||
DocService,
|
||||
FrameworkScope,
|
||||
globalBlockSuiteSchema,
|
||||
GlobalContextService,
|
||||
useLiveData,
|
||||
useService,
|
||||
@@ -36,7 +26,6 @@ import {
|
||||
import clsx from 'clsx';
|
||||
import { memo, useCallback, useEffect, useRef } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { Map as YMap } from 'yjs';
|
||||
|
||||
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
|
||||
import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal';
|
||||
@@ -94,7 +83,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
const activeSidebarTab = useLiveData(view.activeSidebarTab$);
|
||||
|
||||
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
||||
const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper();
|
||||
const { openPage, jumpToPageBlock } = useNavigateHelper();
|
||||
const editorContainer = useLiveData(editor.editorContainer$);
|
||||
|
||||
const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
|
||||
@@ -171,49 +160,10 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
usePageDocumentTitle(title);
|
||||
|
||||
const onLoad = useCallback(
|
||||
(bsPage: BlockSuiteDoc, 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 {}
|
||||
|
||||
(editorContainer: AffineEditorContainer) => {
|
||||
// blocksuite editor 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 =
|
||||
editorHost?.std.getService<PageRootService>('affine:page');
|
||||
const disposable = new DisposableGroup();
|
||||
@@ -234,27 +184,21 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
return openPage(docCollection.id, pageId);
|
||||
})
|
||||
);
|
||||
disposable.add(
|
||||
pageService.slots.tagClicked.on(({ tagId }) => {
|
||||
jumpToTag(workspace.id, tagId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
editor.setEditorContainer(editorContainer);
|
||||
const unbind = editor.bindEditorContainer(
|
||||
editorContainer,
|
||||
(editorContainer as any).docTitle // set from proxy
|
||||
);
|
||||
|
||||
return () => {
|
||||
unbind();
|
||||
editor.setEditorContainer(null);
|
||||
disposable.dispose();
|
||||
};
|
||||
},
|
||||
[
|
||||
editor,
|
||||
jumpToPageBlock,
|
||||
docCollection.id,
|
||||
openPage,
|
||||
jumpToTag,
|
||||
workspace.id,
|
||||
]
|
||||
[editor, openPage, docCollection.id, jumpToPageBlock]
|
||||
);
|
||||
|
||||
const [refCallback, hasScrollTop] = useHasScrollTop();
|
||||
@@ -288,11 +232,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
styles.editorContainer
|
||||
)}
|
||||
>
|
||||
<PageDetailEditor
|
||||
pageId={doc.id}
|
||||
onLoad={onLoad}
|
||||
docCollection={docCollection}
|
||||
/>
|
||||
<PageDetailEditor onLoad={onLoad} />
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar
|
||||
className={clsx({
|
||||
|
||||
@@ -6,17 +6,19 @@ import { AppContainer, MainContainer } from '@affine/core/components/workspace';
|
||||
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
|
||||
import { usePageDocumentTitle } from '@affine/core/hooks/use-global-state';
|
||||
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 { ShareReaderService } from '@affine/core/modules/share-doc';
|
||||
import { CloudBlobStorage } from '@affine/core/modules/workspace-engine';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { type DocMode, DocModes } from '@blocksuite/blocks';
|
||||
import { noop } from '@blocksuite/global/utils';
|
||||
import { Logo1Icon } from '@blocksuite/icons/rc';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Doc as BlockSuiteDoc } from '@blocksuite/store';
|
||||
import type { Doc, Workspace } from '@toeverything/infra';
|
||||
import {
|
||||
DocsService,
|
||||
@@ -105,7 +107,6 @@ const SharePageInner = ({
|
||||
docBinary: Uint8Array;
|
||||
publishMode?: DocMode;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
|
||||
const [workspace, setWorkspace] = useState<Workspace | null>(null);
|
||||
@@ -179,15 +180,24 @@ const SharePageInner = ({
|
||||
const pageTitle = useLiveData(page?.title$);
|
||||
|
||||
usePageDocumentTitle(pageTitle);
|
||||
const authService = useService(AuthService);
|
||||
const loginStatus = useLiveData(authService.session.status$);
|
||||
|
||||
const onEditorLoad = useCallback(
|
||||
(_: BlockSuiteDoc, editor: AffineEditorContainer) => {
|
||||
setActiveBlocksuiteEditor(editor);
|
||||
return noop;
|
||||
(editorContainer: AffineEditorContainer) => {
|
||||
setActiveBlocksuiteEditor(editorContainer);
|
||||
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) {
|
||||
@@ -214,30 +224,12 @@ const SharePageInner = ({
|
||||
styles.editorContainer
|
||||
)}
|
||||
>
|
||||
<PageDetailEditor
|
||||
isPublic
|
||||
publishMode={publishMode}
|
||||
docCollection={page.blockSuiteDoc.collection}
|
||||
pageId={page.id}
|
||||
onLoad={onEditorLoad}
|
||||
/>
|
||||
<PageDetailEditor onLoad={onEditorLoad} />
|
||||
{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}
|
||||
<SharePageFooter />
|
||||
</div>
|
||||
</div>
|
||||
</MainContainer>
|
||||
@@ -248,3 +240,28 @@ const SharePageInner = ({
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user