mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-23 20:18:42 +08:00
feat(core): auto select block when jump to block (#4858)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
@@ -1,16 +1,10 @@
|
|||||||
import type { BlockHub } from '@blocksuite/blocks';
|
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
||||||
import type { Atom } from 'jotai';
|
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import type { HTMLAttributes, ReactElement } from 'react';
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
export interface BlockHubProps extends HTMLAttributes<HTMLDivElement> {
|
export const RootBlockHub = () => {
|
||||||
blockHubAtom: Atom<Readonly<BlockHub> | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BlockHubWrapper = (props: BlockHubProps): ReactElement => {
|
|
||||||
const blockHub = useAtomValue(props.blockHubAtom);
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const blockHub = useAtomValue(rootBlockHubAtom);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const div = ref.current;
|
const div = ref.current;
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import type { BlockHub } from '@blocksuite/blocks';
|
|
||||||
import { EditorContainer } from '@blocksuite/editor';
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { Skeleton } from '@mui/material';
|
import { Skeleton } from '@mui/material';
|
||||||
|
import clsx from 'clsx';
|
||||||
import { use } from 'foxact/use';
|
import { use } from 'foxact/use';
|
||||||
import type { CSSProperties, ReactElement } from 'react';
|
import type { CSSProperties, ReactElement } from 'react';
|
||||||
import { memo, Suspense, useCallback, useEffect, useRef } from 'react';
|
import {
|
||||||
|
memo,
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import type { FallbackProps } from 'react-error-boundary';
|
import type { FallbackProps } from 'react-error-boundary';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
@@ -15,13 +23,17 @@ import {
|
|||||||
} from './index.css';
|
} from './index.css';
|
||||||
import { getPresets } from './preset';
|
import { getPresets } from './preset';
|
||||||
|
|
||||||
|
interface BlockElement extends Element {
|
||||||
|
path: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export type EditorProps = {
|
export type EditorProps = {
|
||||||
page: Page;
|
page: Page;
|
||||||
mode: 'page' | 'edgeless';
|
mode: 'page' | 'edgeless';
|
||||||
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
defaultSelectedBlockId?: string;
|
||||||
onModeChange?: (mode: 'page' | 'edgeless') => void;
|
onModeChange?: (mode: 'page' | 'edgeless') => void;
|
||||||
setBlockHub?: (blockHub: BlockHub | null) => void;
|
// on Editor instance instantiated
|
||||||
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
onLoadEditor?: (editor: EditorContainer) => () => void;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@@ -30,28 +42,62 @@ export type ErrorBoundaryProps = {
|
|||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
// a workaround for returning the webcomponent for the given block id
|
||||||
// eslint-disable-next-line no-var
|
// by iterating over the children of the rendered dom tree
|
||||||
var currentPage: Page | undefined;
|
const useBlockElementById = (
|
||||||
// eslint-disable-next-line no-var
|
container: HTMLElement | null,
|
||||||
var currentEditor: EditorContainer | undefined;
|
blockId: string | undefined,
|
||||||
}
|
timeout = 1000
|
||||||
|
) => {
|
||||||
|
const [blockElement, setBlockElement] = useState<BlockElement | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!blockId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let canceled = false;
|
||||||
|
const start = Date.now();
|
||||||
|
function run() {
|
||||||
|
if (canceled || !container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const element = container.querySelector(
|
||||||
|
`[data-block-id="${blockId}"]`
|
||||||
|
) as BlockElement | null;
|
||||||
|
if (element) {
|
||||||
|
setBlockElement(element);
|
||||||
|
} else if (Date.now() - start < timeout) {
|
||||||
|
setTimeout(run, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run();
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
};
|
||||||
|
}, [container, blockId, timeout]);
|
||||||
|
return blockElement;
|
||||||
|
};
|
||||||
|
|
||||||
const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
const BlockSuiteEditorImpl = ({
|
||||||
const { onLoad, onModeChange, page, mode, style } = props;
|
mode,
|
||||||
|
page,
|
||||||
|
className,
|
||||||
|
defaultSelectedBlockId,
|
||||||
|
onLoadEditor,
|
||||||
|
onModeChange,
|
||||||
|
style,
|
||||||
|
}: EditorProps): ReactElement => {
|
||||||
if (!page.loaded) {
|
if (!page.loaded) {
|
||||||
use(page.waitForLoaded());
|
use(page.waitForLoaded());
|
||||||
}
|
}
|
||||||
assertExists(page, 'page should not be null');
|
assertExists(page, 'page should not be null');
|
||||||
const editorRef = useRef<EditorContainer | null>(null);
|
const editorRef = useRef<EditorContainer | null>(null);
|
||||||
const blockHubRef = useRef<BlockHub | null>(null);
|
|
||||||
if (editorRef.current === null) {
|
if (editorRef.current === null) {
|
||||||
editorRef.current = new EditorContainer();
|
editorRef.current = new EditorContainer();
|
||||||
editorRef.current.autofocus = true;
|
editorRef.current.autofocus = true;
|
||||||
globalThis.currentEditor = editorRef.current;
|
|
||||||
}
|
}
|
||||||
const editor = editorRef.current;
|
const editor = editorRef.current;
|
||||||
assertExists(editorRef, 'editorRef.current should not be null');
|
assertExists(editorRef, 'editorRef.current should not be null');
|
||||||
|
|
||||||
if (editor.mode !== mode) {
|
if (editor.mode !== mode) {
|
||||||
editor.mode = mode;
|
editor.mode = mode;
|
||||||
}
|
}
|
||||||
@@ -64,36 +110,29 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
|||||||
editor.pagePreset = presets.pageModePreset;
|
editor.pagePreset = presets.pageModePreset;
|
||||||
editor.edgelessPreset = presets.edgelessModePreset;
|
editor.edgelessPreset = presets.edgelessModePreset;
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const disposes = [] as ((() => void) | undefined)[];
|
|
||||||
|
|
||||||
if (editor) {
|
if (editor) {
|
||||||
const dispose = editor.slots.pageModeSwitched.on(mode => {
|
const disposes: (() => void)[] = [];
|
||||||
|
const disposeModeSwitch = editor.slots.pageModeSwitched.on(mode => {
|
||||||
onModeChange?.(mode);
|
onModeChange?.(mode);
|
||||||
});
|
});
|
||||||
|
disposes.push(() => disposeModeSwitch?.dispose());
|
||||||
disposes.push(() => dispose?.dispose());
|
if (onLoadEditor) {
|
||||||
|
disposes.push(onLoadEditor(editor));
|
||||||
if (editor.page && onLoad) {
|
|
||||||
disposes.push(onLoad?.(page, editor));
|
|
||||||
}
|
}
|
||||||
|
return () => {
|
||||||
|
disposes.forEach(dispose => dispose());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}, [editor, onModeChange, onLoadEditor]);
|
||||||
|
|
||||||
return () => {
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
disposes
|
|
||||||
.filter((dispose): dispose is () => void => !!dispose)
|
|
||||||
.forEach(dispose => dispose());
|
|
||||||
};
|
|
||||||
}, [editor, editor.page, page, onLoad, onModeChange]);
|
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const setBlockHub = props.setBlockHub;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const editor = editorRef.current;
|
const editor = editorRef.current;
|
||||||
assertExists(editor);
|
assertExists(editor);
|
||||||
const container = ref.current;
|
const container = containerRef.current;
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -103,42 +142,38 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
|||||||
};
|
};
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
|
const blockElement = useBlockElementById(
|
||||||
|
containerRef.current,
|
||||||
|
defaultSelectedBlockId
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (page.meta.trash) {
|
if (blockElement) {
|
||||||
return;
|
requestIdleCallback(() => {
|
||||||
}
|
blockElement.scrollIntoView({
|
||||||
editor
|
behavior: 'smooth',
|
||||||
.createBlockHub()
|
block: 'center',
|
||||||
.then(blockHub => {
|
inline: 'center',
|
||||||
if (blockHubRef.current) {
|
});
|
||||||
blockHubRef.current.remove();
|
const selectManager = editor.root.value?.selection;
|
||||||
|
if (!blockElement.path.length || !selectManager) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
blockHubRef.current = blockHub;
|
const newSelection = selectManager.getInstance('block', {
|
||||||
if (setBlockHub) {
|
path: blockElement.path,
|
||||||
setBlockHub(blockHub);
|
});
|
||||||
}
|
selectManager.set([newSelection]);
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
});
|
||||||
return () => {
|
}
|
||||||
if (setBlockHub) {
|
}, [editor, blockElement]);
|
||||||
setBlockHub(null);
|
|
||||||
}
|
|
||||||
blockHubRef.current?.remove();
|
|
||||||
};
|
|
||||||
}, [editor, page.awarenessStore, page.meta.trash, setBlockHub]);
|
|
||||||
|
|
||||||
// issue: https://github.com/toeverything/AFFiNE/issues/2004
|
// issue: https://github.com/toeverything/AFFiNE/issues/2004
|
||||||
const className = `editor-wrapper ${editor.mode}-mode ${
|
|
||||||
props.className || ''
|
|
||||||
}`;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid={`editor-${page.id}`}
|
data-testid={`editor-${page.id}`}
|
||||||
className={className}
|
className={clsx(`editor-wrapper ${editor.mode}-mode`, className)}
|
||||||
style={style}
|
style={style}
|
||||||
ref={ref}
|
ref={containerRef}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type {
|
|||||||
WorkspaceFlavour,
|
WorkspaceFlavour,
|
||||||
WorkspaceUISchema,
|
WorkspaceUISchema,
|
||||||
} from '@affine/env/workspace';
|
} from '@affine/env/workspace';
|
||||||
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
|
||||||
import { lazy, useCallback } from 'react';
|
import { lazy, useCallback } from 'react';
|
||||||
|
|
||||||
import type { OnLoadEditor } from '../../components/page-detail-editor';
|
import type { OnLoadEditor } from '../../components/page-detail-editor';
|
||||||
@@ -50,7 +49,6 @@ export const UI = {
|
|||||||
return (
|
return (
|
||||||
<PageDetailEditor
|
<PageDetailEditor
|
||||||
pageId={currentPageId}
|
pageId={currentPageId}
|
||||||
onInit={useCallback(async page => initEmptyPage(page), [])}
|
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
workspace={workspace.blockSuiteWorkspace}
|
workspace={workspace.blockSuiteWorkspace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
|||||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { setPageModeAtom } from '../../atoms';
|
import { setPageModeAtom } from '../../atoms';
|
||||||
import {
|
import {
|
||||||
@@ -94,7 +93,6 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
|||||||
return (
|
return (
|
||||||
<PageDetailEditor
|
<PageDetailEditor
|
||||||
pageId={currentPageId}
|
pageId={currentPageId}
|
||||||
onInit={useCallback(async page => initEmptyPage(page), [])}
|
|
||||||
onLoad={onLoadEditor}
|
onLoad={onLoadEditor}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { PageNotFoundError } from '@affine/env/constant';
|
import { PageNotFoundError } from '@affine/env/constant';
|
||||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { type WorkspaceUISchema } from '@affine/env/workspace';
|
import { type WorkspaceUISchema } from '@affine/env/workspace';
|
||||||
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { useWorkspace } from '../../hooks/use-workspace';
|
import { useWorkspace } from '../../hooks/use-workspace';
|
||||||
import { PageDetailEditor, Provider } from '../shared';
|
import { PageDetailEditor, Provider } from '../shared';
|
||||||
@@ -18,7 +16,6 @@ export const UI = {
|
|||||||
return (
|
return (
|
||||||
<PageDetailEditor
|
<PageDetailEditor
|
||||||
pageId={currentPageId}
|
pageId={currentPageId}
|
||||||
onInit={useCallback(async page => initEmptyPage(page), [])}
|
|
||||||
onLoad={onLoadEditor}
|
onLoad={onLoadEditor}
|
||||||
workspace={workspace.blockSuiteWorkspace}
|
workspace={workspace.blockSuiteWorkspace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import './page-detail-editor.css';
|
import './page-detail-editor.css';
|
||||||
|
|
||||||
import { PageNotFoundError } from '@affine/env/constant';
|
import { PageNotFoundError } from '@affine/env/constant';
|
||||||
import type { LayoutNode } from '@affine/sdk//entry';
|
import type { LayoutNode } from '@affine/sdk/entry';
|
||||||
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
||||||
|
import type { BlockHub } from '@blocksuite/blocks';
|
||||||
import type { EditorContainer } from '@blocksuite/editor';
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
import { assertExists, DisposableGroup } from '@blocksuite/global/utils';
|
import { assertExists, DisposableGroup } from '@blocksuite/global/utils';
|
||||||
import type { Page, Workspace } from '@blocksuite/store';
|
import type { Page, Workspace } from '@blocksuite/store';
|
||||||
@@ -40,8 +41,9 @@ import * as styles from './page-detail-editor.css';
|
|||||||
import { editorContainer, pluginContainer } from './page-detail-editor.css';
|
import { editorContainer, pluginContainer } from './page-detail-editor.css';
|
||||||
import { TrashButtonGroup } from './pure/trash-button-group';
|
import { TrashButtonGroup } from './pure/trash-button-group';
|
||||||
|
|
||||||
function useRouterHash() {
|
declare global {
|
||||||
return useLocation().hash.substring(1);
|
// eslint-disable-next-line no-var
|
||||||
|
var currentEditor: EditorContainer | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnLoadEditor = (page: Page, editor: EditorContainer) => () => void;
|
export type OnLoadEditor = (page: Page, editor: EditorContainer) => () => void;
|
||||||
@@ -50,17 +52,43 @@ export interface PageDetailEditorProps {
|
|||||||
isPublic?: boolean;
|
isPublic?: boolean;
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
pageId: string;
|
pageId: string;
|
||||||
onInit: (
|
|
||||||
page: Page,
|
|
||||||
editor: Readonly<EditorContainer>
|
|
||||||
) => Promise<void> | void;
|
|
||||||
onLoad?: OnLoadEditor;
|
onLoad?: OnLoadEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useRouterHash() {
|
||||||
|
return useLocation().hash.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useCreateAndSetRootBlockHub(
|
||||||
|
editor?: EditorContainer,
|
||||||
|
showBlockHub?: boolean
|
||||||
|
) {
|
||||||
|
const setBlockHub = useSetAtom(rootBlockHubAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
let canceled = false;
|
||||||
|
let blockHub: BlockHub | undefined;
|
||||||
|
if (editor && showBlockHub) {
|
||||||
|
editor
|
||||||
|
.createBlockHub()
|
||||||
|
.then(bh => {
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blockHub = bh;
|
||||||
|
setBlockHub(blockHub);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
blockHub?.remove();
|
||||||
|
};
|
||||||
|
}, [editor, showBlockHub, setBlockHub]);
|
||||||
|
}
|
||||||
|
|
||||||
const EditorWrapper = memo(function EditorWrapper({
|
const EditorWrapper = memo(function EditorWrapper({
|
||||||
workspace,
|
workspace,
|
||||||
pageId,
|
pageId,
|
||||||
onInit,
|
|
||||||
onLoad,
|
onLoad,
|
||||||
isPublic,
|
isPublic,
|
||||||
}: PageDetailEditorProps) {
|
}: PageDetailEditorProps) {
|
||||||
@@ -79,7 +107,6 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
const pageSetting = useAtomValue(pageSettingAtom);
|
const pageSetting = useAtomValue(pageSettingAtom);
|
||||||
const currentMode = pageSetting?.mode ?? 'page';
|
const currentMode = pageSetting?.mode ?? 'page';
|
||||||
|
|
||||||
const setBlockHub = useSetAtom(rootBlockHubAtom);
|
|
||||||
const { appSettings } = useAppSettingHelper();
|
const { appSettings } = useAppSettingHelper();
|
||||||
|
|
||||||
assertExists(meta);
|
assertExists(meta);
|
||||||
@@ -91,29 +118,6 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
return fontStyle.value;
|
return fontStyle.value;
|
||||||
}, [appSettings.fontStyle]);
|
}, [appSettings.fontStyle]);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const blockId = useRouterHash();
|
|
||||||
const blockElement = useMemo(() => {
|
|
||||||
if (!blockId || loading) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return document.querySelector(`[data-block-id="${blockId}"]`);
|
|
||||||
}, [blockId, loading]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (blockElement) {
|
|
||||||
setTimeout(
|
|
||||||
() =>
|
|
||||||
blockElement.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center',
|
|
||||||
inline: 'center',
|
|
||||||
}),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [blockElement]);
|
|
||||||
|
|
||||||
const setEditorMode = useCallback(
|
const setEditorMode = useCallback(
|
||||||
(mode: 'page' | 'edgeless') => {
|
(mode: 'page' | 'edgeless') => {
|
||||||
if (mode === 'edgeless') {
|
if (mode === 'edgeless') {
|
||||||
@@ -125,6 +129,56 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
[switchToEdgelessMode, switchToPageMode, pageId]
|
[switchToEdgelessMode, switchToPageMode, pageId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [editor, setEditor] = useState<EditorContainer>();
|
||||||
|
const blockId = useRouterHash();
|
||||||
|
|
||||||
|
useCreateAndSetRootBlockHub(editor, !meta.trash);
|
||||||
|
|
||||||
|
const onLoadEditor = useCallback(
|
||||||
|
(editor: EditorContainer) => {
|
||||||
|
// debug current detail editor
|
||||||
|
globalThis.currentEditor = editor;
|
||||||
|
setEditor(editor);
|
||||||
|
const disposableGroup = new DisposableGroup();
|
||||||
|
disposableGroup.add(
|
||||||
|
page.slots.blockUpdated.once(() => {
|
||||||
|
page.workspace.setPageMeta(page.id, {
|
||||||
|
updatedDate: Date.now(),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
localStorage.setItem('last_page_id', page.id);
|
||||||
|
if (onLoad) {
|
||||||
|
disposableGroup.add(onLoad(page, editor));
|
||||||
|
}
|
||||||
|
const rootStore = getCurrentStore();
|
||||||
|
const editorItems = rootStore.get(pluginEditorAtom);
|
||||||
|
let disposes: (() => void)[] = [];
|
||||||
|
const renderTimeout = window.setTimeout(() => {
|
||||||
|
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.setAttribute('plugin-id', id);
|
||||||
|
const cleanup = editorItem(div, editor);
|
||||||
|
assertExists(parent);
|
||||||
|
document.body.appendChild(div);
|
||||||
|
return () => {
|
||||||
|
cleanup();
|
||||||
|
document.body.removeChild(div);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
disposableGroup.dispose();
|
||||||
|
clearTimeout(renderTimeout);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
disposes.forEach(dispose => dispose());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[onLoad, page]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Editor
|
<Editor
|
||||||
@@ -140,55 +194,8 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
mode={isPublic ? 'page' : currentMode}
|
mode={isPublic ? 'page' : currentMode}
|
||||||
page={page}
|
page={page}
|
||||||
onModeChange={setEditorMode}
|
onModeChange={setEditorMode}
|
||||||
onInit={useCallback(
|
defaultSelectedBlockId={blockId}
|
||||||
(page: Page, editor: Readonly<EditorContainer>) => {
|
onLoadEditor={onLoadEditor}
|
||||||
onInit(page, editor);
|
|
||||||
},
|
|
||||||
[onInit]
|
|
||||||
)}
|
|
||||||
setBlockHub={setBlockHub}
|
|
||||||
onLoad={useCallback(
|
|
||||||
(page: Page, editor: EditorContainer) => {
|
|
||||||
const disposableGroup = new DisposableGroup();
|
|
||||||
disposableGroup.add(
|
|
||||||
page.slots.blockUpdated.once(() => {
|
|
||||||
page.workspace.setPageMeta(page.id, {
|
|
||||||
updatedDate: Date.now(),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
localStorage.setItem('last_page_id', page.id);
|
|
||||||
if (onLoad) {
|
|
||||||
disposableGroup.add(onLoad(page, editor));
|
|
||||||
}
|
|
||||||
const rootStore = getCurrentStore();
|
|
||||||
const editorItems = rootStore.get(pluginEditorAtom);
|
|
||||||
let disposes: (() => void)[] = [];
|
|
||||||
const renderTimeout = window.setTimeout(() => {
|
|
||||||
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.setAttribute('plugin-id', id);
|
|
||||||
const cleanup = editorItem(div, editor);
|
|
||||||
assertExists(parent);
|
|
||||||
document.body.appendChild(div);
|
|
||||||
return () => {
|
|
||||||
cleanup();
|
|
||||||
document.body.removeChild(div);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
disposableGroup.dispose();
|
|
||||||
clearTimeout(renderTimeout);
|
|
||||||
window.setTimeout(() => {
|
|
||||||
disposes.forEach(dispose => dispose());
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[onLoad]
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
{meta.trash && <TrashButtonGroup />}
|
{meta.trash && <TrashButtonGroup />}
|
||||||
<Bookmark page={page} />
|
<Bookmark page={page} />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
AppSidebarFallback,
|
AppSidebarFallback,
|
||||||
appSidebarResizingAtom,
|
appSidebarResizingAtom,
|
||||||
} from '@affine/component/app-sidebar';
|
} from '@affine/component/app-sidebar';
|
||||||
import { BlockHubWrapper } from '@affine/component/block-hub';
|
import { RootBlockHub } from '@affine/component/block-hub';
|
||||||
import {
|
import {
|
||||||
type DraggableTitleCellData,
|
type DraggableTitleCellData,
|
||||||
PageListDragOverlay,
|
PageListDragOverlay,
|
||||||
@@ -13,10 +13,7 @@ import {
|
|||||||
WorkspaceFallback,
|
WorkspaceFallback,
|
||||||
} from '@affine/component/workspace';
|
} from '@affine/component/workspace';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import {
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
rootBlockHubAtom,
|
|
||||||
rootWorkspacesMetadataAtom,
|
|
||||||
} from '@affine/workspace/atom';
|
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import type { DragEndEvent } from '@dnd-kit/core';
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
@@ -294,7 +291,7 @@ export const WorkspaceLayoutInner = ({
|
|||||||
>
|
>
|
||||||
{incompatible ? <WorkspaceUpgrade /> : children}
|
{incompatible ? <WorkspaceUpgrade /> : children}
|
||||||
<ToolContainer inTrashPage={inTrashPage}>
|
<ToolContainer inTrashPage={inTrashPage}>
|
||||||
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
|
<RootBlockHub />
|
||||||
<HelpIsland showList={pageId ? undefined : showList} />
|
<HelpIsland showList={pageId ? undefined : showList} />
|
||||||
</ToolContainer>
|
</ToolContainer>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ export const Component = (): ReactElement => {
|
|||||||
isPublic
|
isPublic
|
||||||
workspace={page.workspace}
|
workspace={page.workspace}
|
||||||
pageId={page.id}
|
pageId={page.id}
|
||||||
onInit={noop}
|
|
||||||
onLoad={useCallback(() => noop, [])}
|
onLoad={useCallback(() => noop, [])}
|
||||||
/>
|
/>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ test('show not found item', async ({ page }) => {
|
|||||||
await expect(notFoundItem).toHaveText('Search for "test123456"');
|
await expect(notFoundItem).toHaveText('Search for "test123456"');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can use cmdk to search page content and scroll to it', async ({
|
test('can use cmdk to search page content and scroll to it, then the block will be selected', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await openHomePage(page);
|
await openHomePage(page);
|
||||||
@@ -382,8 +382,8 @@ test('can use cmdk to search page content and scroll to it', async ({
|
|||||||
await getBlockSuiteEditorTitle(page).fill(
|
await getBlockSuiteEditorTitle(page).fill(
|
||||||
'this is a new page to search for content'
|
'this is a new page to search for content'
|
||||||
);
|
);
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 30; i++) {
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter', { delay: 10 });
|
||||||
}
|
}
|
||||||
await page.keyboard.insertText('123456');
|
await page.keyboard.insertText('123456');
|
||||||
await clickSideBarAllPageButton(page);
|
await clickSideBarAllPageButton(page);
|
||||||
@@ -398,4 +398,6 @@ test('can use cmdk to search page content and scroll to it', async ({
|
|||||||
await waitForScrollToFinish(page);
|
await waitForScrollToFinish(page);
|
||||||
const isVisitable = await checkElementIsInView(page, '123456');
|
const isVisitable = await checkElementIsInView(page, '123456');
|
||||||
expect(isVisitable).toBe(true);
|
expect(isVisitable).toBe(true);
|
||||||
|
const selectionElement = page.locator('affine-block-selection');
|
||||||
|
await expect(selectionElement).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { BlockHubWrapper } from '@affine/component/block-hub';
|
import { RootBlockHub } from '@affine/component/block-hub';
|
||||||
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
|
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
import { ImagePreviewModal } from '@affine/image-preview-plugin/src/component';
|
import { ImagePreviewModal } from '@affine/image-preview-plugin/src/component';
|
||||||
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
|
||||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||||
import type { Meta } from '@storybook/react';
|
import type { Meta } from '@storybook/react';
|
||||||
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -54,24 +52,13 @@ export const Default = () => {
|
|||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BlockSuiteEditor
|
<BlockSuiteEditor mode="page" page={page} />
|
||||||
mode="page"
|
|
||||||
page={page}
|
|
||||||
onInit={useCallback(async page => initEmptyPage(page), [])}
|
|
||||||
/>
|
|
||||||
{createPortal(
|
{createPortal(
|
||||||
<ImagePreviewModal pageId={page.id} workspace={page.workspace} />,
|
<ImagePreviewModal pageId={page.id} workspace={page.workspace} />,
|
||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<BlockHubWrapper
|
<RootBlockHub />
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
right: 12,
|
|
||||||
bottom: 12,
|
|
||||||
}}
|
|
||||||
blockHubAtom={rootBlockHubAtom}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user