From 01b23391735ab6a49ab1a1930ce27eccc67353bf Mon Sep 17 00:00:00 2001 From: EYHN Date: Wed, 4 Sep 2024 08:13:21 +0000 Subject: [PATCH] refactor(core): better block link (#8082) --- .../affine/reference-link/index.tsx | 66 +++---- .../blocksuite-editor-container.tsx | 68 +++----- .../block-suite-editor/blocksuite-editor.tsx | 13 +- .../block-suite-editor/lit-adaper.tsx | 6 +- .../block-suite-mode-switch/index.tsx | 2 + .../src/components/page-detail-editor.tsx | 5 +- .../src/modules/editor/entities/editor.ts | 164 +++++++++++++++++- .../src/modules/editor/services/editors.ts | 9 +- .../frontend/core/src/modules/editor/types.ts | 1 + .../editor/utils/scroll-anchoring.ts} | 1 + .../view/doc-preview/doc-peek-view.tsx | 106 +++++------ .../core/src/modules/peek-view/view/utils.ts | 9 +- .../src/modules/workbench/entities/view.ts | 6 +- .../core/src/modules/workbench/index.ts | 1 + .../detail-page/detail-page-wrapper.tsx | 71 +------- .../workspace/detail-page/detail-page.tsx | 4 +- .../src/pages/workspace/share/share-page.tsx | 3 +- 17 files changed, 282 insertions(+), 253 deletions(-) rename packages/frontend/core/src/{components/affine/reference-link/utils.ts => modules/editor/utils/scroll-anchoring.ts} (98%) diff --git a/packages/frontend/core/src/components/affine/reference-link/index.tsx b/packages/frontend/core/src/components/affine/reference-link/index.tsx index a49357dbac..977635702d 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -6,7 +6,6 @@ import { } from '@affine/core/modules/peek-view'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; -import type { BlockStdScope } from '@blocksuite/block-std'; import type { DocMode } from '@blocksuite/blocks'; import { BlockLinkIcon, @@ -17,16 +16,16 @@ import { } from '@blocksuite/icons/rc'; import type { DocCollection } from '@blocksuite/store'; import { useService } from '@toeverything/infra'; +import { nanoid } from 'nanoid'; import { type PropsWithChildren, useCallback, - useEffect, + useMemo, useRef, useState, } from 'react'; import * as styles from './styles.css'; -import { scrollAnchoring } from './utils'; export interface PageReferenceRendererOptions { pageId: string; @@ -90,8 +89,6 @@ export function AffinePageReference({ wrapper: Wrapper, mode = 'page', params = {}, - isSameDoc = false, - std, }: { pageId: string; docCollection: DocCollection; @@ -102,16 +99,10 @@ export function AffinePageReference({ blockIds?: string[]; elementIds?: string[]; }; - isSameDoc?: boolean; - std?: BlockStdScope; }) { const pageMetaHelper = useDocMetaHelper(docCollection); const journalHelper = useJournalHelper(docCollection); const t = useI18n(); - const [anchor, setAnchor] = useState<{ - mode: DocMode; - id: string; - } | null>(null); const { mode: linkedWithMode, blockIds, elementIds } = params; @@ -128,21 +119,11 @@ export function AffinePageReference({ const ref = useRef(null); + const [refreshKey, setRefreshKey] = useState(() => nanoid()); + const peekView = useService(PeekViewService).peekView; const isInPeekView = useInsidePeekView(); - useEffect(() => { - if (isSameDoc) { - if (mode === 'edgeless' && elementIds?.length) { - setAnchor({ mode, id: elementIds[0] }); - } else if (blockIds?.length) { - setAnchor({ mode, id: blockIds[0] }); - } - } else { - setAnchor(null); - } - }, [std, isSameDoc, mode, blockIds, elementIds]); - const onClick = useCallback( (e: React.MouseEvent) => { if (e.shiftKey && ref.current) { @@ -151,33 +132,34 @@ export function AffinePageReference({ peekView.open(ref.current).catch(console.error); } - if (std && anchor) { - e.preventDefault(); - e.stopPropagation(); - const { mode, id } = anchor; - scrollAnchoring(std, mode, id); - } else if (isInPeekView) { + if (isInPeekView) { peekView.close(); } + // update refresh key + setRefreshKey(nanoid()); + return; }, - [isInPeekView, peekView, anchor, std] + [isInPeekView, peekView] ); - // A block/element reference link - const search = new URLSearchParams(); - if (linkedWithMode) { - search.set('mode', linkedWithMode); - } - if (blockIds?.length) { - search.set('blockIds', blockIds.join(',')); - } - if (elementIds?.length) { - search.set('elementIds', elementIds.join(',')); - } + const query = useMemo(() => { + // A block/element reference link + const search = new URLSearchParams(); + if (linkedWithMode) { + search.set('mode', linkedWithMode); + } + if (blockIds?.length) { + search.set('blockIds', blockIds.join(',')); + } + if (elementIds?.length) { + search.set('elementIds', elementIds.join(',')); + } + search.set('refreshKey', refreshKey); - const query = search.size > 0 ? `?${search.toString()}` : ''; + return search.size > 0 ? `?${search.toString()}` : ''; + }, [blockIds, elementIds, linkedWithMode, refreshKey]); return ( (function AffineEditorContainer( - { page, mode, className, style, shared, defaultEditorSelector }, + { page, mode, className, style, shared }, ref ) { + const editorService = useService(EditorService); const rootRef = useRef(null); const docRef = useRef(null); const docTitleRef = useRef(null); const edgelessRef = useRef(null); - const [anchor] = useState(() => { - if (mode === 'edgeless' && defaultEditorSelector?.elementIds?.length) { - return defaultEditorSelector.elementIds[0]; - } else if (defaultEditorSelector?.blockIds?.length) { - return defaultEditorSelector.blockIds[0]; - } - return null; - }); const slots: BlocksuiteEditorContainerRef['slots'] = useMemo(() => { return { @@ -180,40 +170,24 @@ export const BlocksuiteEditorContainer = forwardRef< }, [affineEditorContainerProxy, ref]); useEffect(() => { - if (anchor) { - let canceled = false; - affineEditorContainerProxy.updateComplete - .then(() => { - if (!canceled) { - const std = affineEditorContainerProxy.host?.std; - if (std) { - scrollAnchoring(std, mode, anchor); - } - } - }) - .catch(console.error); - return () => { - canceled = true; - }; - } else { - // if no anchor, focus the title - let canceled = false; + let canceled = false; + let unsubscribe: () => void = () => {}; - affineEditorContainerProxy.updateComplete - .then(() => { - if (!canceled) { - const title = docTitleRef.current?.querySelector< - HTMLElement & { inlineEditor: InlineEditor } - >('rich-text'); - title?.inlineEditor.focusEnd(); - } - }) - .catch(console.error); - return () => { - canceled = true; - }; - } - }, [anchor, affineEditorContainerProxy, mode]); + 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(() => { affineEditorContainerProxy.host?.std.command.exec( diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/blocksuite-editor.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/blocksuite-editor.tsx index 66875655d6..aa1fcdd795 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/blocksuite-editor.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/blocksuite-editor.tsx @@ -1,5 +1,4 @@ import { EditorLoading } from '@affine/component/page-detail-skeleton'; -import type { EditorSelector } from '@affine/core/modules/editor'; import type { DocMode } from '@blocksuite/blocks'; import { assertExists } from '@blocksuite/global/utils'; import type { AffineEditorContainer } from '@blocksuite/presets'; @@ -30,7 +29,6 @@ export type EditorProps = { onLoadEditor?: (editor: AffineEditorContainer) => () => void; style?: CSSProperties; className?: string; - defaultEditorSelector?: EditorSelector; }; function usePageRoot(page: Doc) { @@ -57,15 +55,7 @@ function usePageRoot(page: Doc) { const BlockSuiteEditorImpl = forwardRef( function BlockSuiteEditorImpl( - { - mode, - page, - className, - onLoadEditor, - shared, - style, - defaultEditorSelector, - }, + { mode, page, className, onLoadEditor, shared, style }, ref ) { usePageRoot(page); @@ -115,7 +105,6 @@ const BlockSuiteEditorImpl = forwardRef( ref={onRefChange} className={className} style={style} - defaultEditorSelector={defaultEditorSelector} /> ); } diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx index 437fee41b7..5e297e81c8 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx @@ -77,20 +77,16 @@ const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => { const pageId = data.pageId; if (!pageId) return ; - const isSameDoc = pageId === page.id; - return ( ); }; - }, [mode, page.collection, page.id]); + }, [mode, page.collection]); const specs = useMemo(() => { return mode === 'edgeless' diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx index 24271422e5..a350245201 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx @@ -40,6 +40,7 @@ export const EditorModeSwitch = () => { const togglePage = useCallback(() => { if (currentMode === 'page' || isSharedMode || trash) return; editor.setMode('page'); + editor.setSelector(undefined); editor.doc.setPrimaryMode('page'); toast(t['com.affine.toastMessage.pageMode']()); track.$.header.actions.switchPageMode({ mode: 'page' }); @@ -48,6 +49,7 @@ export const EditorModeSwitch = () => { const toggleEdgeless = useCallback(() => { if (currentMode === 'edgeless' || isSharedMode || trash) return; editor.setMode('edgeless'); + editor.setSelector(undefined); editor.doc.setPrimaryMode('edgeless'); toast(t['com.affine.toastMessage.edgelessMode']()); track.$.header.actions.switchPageMode({ mode: 'edgeless' }); diff --git a/packages/frontend/core/src/components/page-detail-editor.tsx b/packages/frontend/core/src/components/page-detail-editor.tsx index 9eddee14e6..8d02c03697 100644 --- a/packages/frontend/core/src/components/page-detail-editor.tsx +++ b/packages/frontend/core/src/components/page-detail-editor.tsx @@ -11,7 +11,7 @@ import clsx from 'clsx'; import type { CSSProperties } from 'react'; import { memo, useCallback, useMemo } from 'react'; -import { type EditorSelector, EditorService } from '../modules/editor'; +import { EditorService } from '../modules/editor'; import { EditorSettingService, fontStyleOptions, @@ -35,13 +35,11 @@ export interface PageDetailEditorProps { docCollection: DocCollection; pageId: string; onLoad?: OnLoadEditor; - defaultEditorSelector?: EditorSelector; } const PageDetailEditorMain = memo(function PageDetailEditorMain({ page, onLoad, - defaultEditorSelector, }: PageDetailEditorProps & { page: BlockSuiteDoc }) { const editor = useService(EditorService).editor; const mode = useLiveData(editor.mode$); @@ -108,7 +106,6 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({ mode={mode} page={page} shared={isSharedMode} - defaultEditorSelector={defaultEditorSelector} onLoadEditor={onLoadEditor} /> ); diff --git a/packages/frontend/core/src/modules/editor/entities/editor.ts b/packages/frontend/core/src/modules/editor/entities/editor.ts index 875200d33c..225b693bf8 100644 --- a/packages/frontend/core/src/modules/editor/entities/editor.ts +++ b/packages/frontend/core/src/modules/editor/entities/editor.ts @@ -1,29 +1,32 @@ import type { DocMode } from '@blocksuite/blocks'; -import type { AffineEditorContainer } from '@blocksuite/presets'; +import type { InlineEditor } from '@blocksuite/inline/inline-editor'; +import type { AffineEditorContainer, DocTitle } from '@blocksuite/presets'; import type { DocService, WorkspaceService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; +import { isEqual } from 'lodash-es'; +import type { WorkbenchView } from '../../workbench'; import { EditorScope } from '../scopes/editor'; import type { EditorSelector } from '../types'; +import { scrollAnchoring } from '../utils/scroll-anchoring'; -export class Editor extends Entity<{ - defaultMode: DocMode; - defaultEditorSelector?: EditorSelector; -}> { +export class Editor extends Entity { readonly scope = this.framework.createScope(EditorScope, { editor: this as Editor, }); - readonly mode$ = new LiveData(this.props.defaultMode); - readonly selector$ = new LiveData( - this.props.defaultEditorSelector - ); + readonly mode$ = new LiveData('page'); + readonly selector$ = new LiveData(undefined); readonly doc = this.docService.doc; readonly isSharedMode = this.workspaceService.workspace.openOptions.isSharedMode; readonly editorContainer$ = new LiveData(null); + setSelector(selector: EditorSelector | undefined) { + this.selector$.next(selector); + } + toggleMode() { this.mode$.next(this.mode$.value === 'edgeless' ? 'page' : 'edgeless'); } @@ -36,6 +39,149 @@ export class Editor extends Entity<{ this.editorContainer$.next(editorContainer); } + /** + * sync editor params with view query string + */ + bindWorkbenchView(view: WorkbenchView) { + // eslint-disable-next-line rxjs/finnish + const viewParams$ = view.queryString$<{ + mode?: DocMode; + blockIds?: string[]; + elementIds?: string[]; + refreshKey?: string; + }>({ + // Cannot handle single id situation correctly: `blockIds=xxx` + arrayFormat: 'none', + types: { + mode: value => + value === 'page' || value === 'edgeless' ? value : undefined, + blockIds: value => (value.length ? value.split(',') : []), + elementIds: value => (value.length ? value.split(',') : []), + refreshKey: 'string', + }, + }); + + const stablePrimaryMode = this.doc.getPrimaryMode(); + + const editorParams$ = LiveData.computed(get => { + const selector = get(this.selector$); + return { + mode: get(this.mode$), + blockIds: selector?.blockIds, + elementIds: selector?.elementIds, + refreshKey: selector?.refreshKey, + }; + }); + + // prevent infinite loop + let updating = false; + + const unsubscribeViewParams = viewParams$.subscribe(params => { + if (updating) return; + updating = true; + // when view params changed, sync to editor + try { + const mode = + viewParams$.value.mode || stablePrimaryMode || ('page' as DocMode); + if (mode !== editorParams$.value.mode) { + this.setMode(mode); + } + const newSelector = { + blockIds: params.blockIds, + elementIds: params.elementIds, + refreshKey: params.refreshKey, + }; + if (!isEqual(newSelector, editorParams$.value)) { + this.setSelector({ + blockIds: params.blockIds, + elementIds: params.elementIds, + refreshKey: params.refreshKey, + }); + } + } finally { + updating = false; + } + }); + + const unsubscribeEditorParams = editorParams$.subscribe(params => { + if (updating) return; + updating = true; + try { + // when editor params changed, sync to view + const newQueryString: any = {}; + let updated = false; + if (params.mode !== viewParams$.value.mode) { + newQueryString.mode = params.mode; + updated = true; + } + const stringBlockIds = params.blockIds?.join(','); + const stringElementIds = params.elementIds?.join(','); + const stringViewBlockIds = viewParams$.value.blockIds?.join(','); + const stringViewElementIds = viewParams$.value.elementIds?.join(','); + if ( + stringBlockIds !== stringViewBlockIds || + stringElementIds !== stringViewElementIds || + params.refreshKey !== viewParams$.value.refreshKey + ) { + newQueryString.blockIds = stringBlockIds; + newQueryString.elementIds = stringElementIds; + newQueryString.refreshKey = params.refreshKey; + updated = true; + } + + if (updated) { + view.updateQueryString(newQueryString, { replace: true }); + } + } finally { + updating = false; + } + }); + + return () => { + unsubscribeEditorParams.unsubscribe(); + unsubscribeViewParams.unsubscribe(); + }; + } + + bindEditorContainer( + editorContainer: AffineEditorContainer, + docTitle: DocTitle + ) { + const focusAt$ = LiveData.computed(get => { + const selector = get(this.selector$); + const id = + get(this.mode$) === 'edgeless' && selector?.elementIds?.length + ? selector?.elementIds?.[0] + : selector?.blockIds?.[0]; + if (id) { + return { id, refreshKey: selector?.refreshKey }; + } else { + return null; + } + }); + if (focusAt$.value === null) { + 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); + } + } + }); + return () => { + unsubscribe.unsubscribe(); + }; + } + constructor( private readonly docService: DocService, private readonly workspaceService: WorkspaceService diff --git a/packages/frontend/core/src/modules/editor/services/editors.ts b/packages/frontend/core/src/modules/editor/services/editors.ts index 189df432c9..ee38b5415a 100644 --- a/packages/frontend/core/src/modules/editor/services/editors.ts +++ b/packages/frontend/core/src/modules/editor/services/editors.ts @@ -1,14 +1,9 @@ -import type { DocMode } from '@blocksuite/blocks'; import { Service } from '@toeverything/infra'; import { Editor } from '../entities/editor'; -import type { EditorSelector } from '../types'; export class EditorsService extends Service { - createEditor(defaultMode: DocMode, defaultEditorSelector?: EditorSelector) { - return this.framework.createEntity(Editor, { - defaultMode, - defaultEditorSelector, - }); + createEditor() { + return this.framework.createEntity(Editor); } } diff --git a/packages/frontend/core/src/modules/editor/types.ts b/packages/frontend/core/src/modules/editor/types.ts index b08e002323..f5e4eae7db 100644 --- a/packages/frontend/core/src/modules/editor/types.ts +++ b/packages/frontend/core/src/modules/editor/types.ts @@ -1,4 +1,5 @@ export type EditorSelector = { blockIds?: string[]; elementIds?: string[]; + refreshKey?: string; }; diff --git a/packages/frontend/core/src/components/affine/reference-link/utils.ts b/packages/frontend/core/src/modules/editor/utils/scroll-anchoring.ts similarity index 98% rename from packages/frontend/core/src/components/affine/reference-link/utils.ts rename to packages/frontend/core/src/modules/editor/utils/scroll-anchoring.ts index f7318ca218..6fc06a0378 100644 --- a/packages/frontend/core/src/components/affine/reference-link/utils.ts +++ b/packages/frontend/core/src/modules/editor/utils/scroll-anchoring.ts @@ -39,6 +39,7 @@ function scrollAnchoringInEdgelessMode( h / service.viewport.zoom ); } else { + if (!service.getElementById) return; const model = service.getElementById(id); if (!model) return; diff --git a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx index a8eea0f3ab..4e1cb48013 100644 --- a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx @@ -5,16 +5,17 @@ import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error 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'; import type { DocMode, EdgelessRootService } from '@blocksuite/blocks'; import { Bound, DisposableGroup } from '@blocksuite/global/utils'; import type { AffineEditorContainer } from '@blocksuite/presets'; import { - DocsService, FrameworkScope, useLiveData, useService, + useServices, } from '@toeverything/infra'; import clsx from 'clsx'; import { useCallback, useEffect, useState } from 'react'; @@ -62,27 +63,21 @@ function fitViewport( } } -export function DocPeekPreview({ - docId, - blockIds, - elementIds, - mode, +function DocPeekPreviewEditor({ xywh, }: { - docId: string; - blockIds?: string[]; - elementIds?: string[]; - mode?: DocMode; xywh?: `[${number},${number},${number},${number}]`; }) { - const { doc, workspace, editor, loading } = useEditor(docId, mode, { - blockIds, - elementIds, + const { editorService } = useServices({ + EditorService, }); + const editor = editorService.editor; + 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 defaultEditorSelector = useLiveData(editor?.selector$); const [editorElement, setEditorElement] = useState(null); @@ -90,27 +85,16 @@ export function DocPeekPreview({ setEditorElement(editor); }; - const docs = useService(DocsService); - const [resolvedMode, setResolvedMode] = useState(mode); - useEffect(() => { editorElement?.updateComplete .then(() => { - if (resolvedMode === 'edgeless') { + if (mode === 'edgeless') { fitViewport(editorElement, xywh); } }) .catch(console.error); return; - }, [editorElement, resolvedMode, xywh]); - - useEffect(() => { - if (!mode || !resolvedMode) { - setResolvedMode( - docs.list.doc$(docId).value?.primaryMode$.value || 'page' - ); - } - }, [docId, docs.list, resolvedMode, mode]); + }, [editorElement, mode, xywh]); useEffect(() => { const disposable = AIProvider.slots.requestOpenWithChat.on(() => { @@ -164,20 +148,11 @@ export function DocPeekPreview({ }, [editorElement, jumpToTag, peekView, workspace.id]); const openOutlinePanel = useCallback(() => { - workbench.openDoc(docId); + workbench.openDoc(doc.id); workbench.openSidebar(); workbench.activeView$.value.activeSidebarTab('outline'); peekView.close(); - }, [docId, peekView, workbench]); - - // if sync engine has been synced and the page is null, show 404 page. - if (!doc || !resolvedMode || !editor) { - return loading || !resolvedMode ? ( - - ) : ( - - ); - } + }, [doc, peekView, workbench]); return ( @@ -185,20 +160,15 @@ export function DocPeekPreview({ - - - - - + @@ -208,3 +178,39 @@ export function DocPeekPreview({ ); } + +export function DocPeekPreview({ + docId, + blockIds, + elementIds, + mode, + xywh, +}: { + docId: string; + blockIds?: string[]; + elementIds?: string[]; + mode?: DocMode; + xywh?: `[${number},${number},${number},${number}]`; +}) { + const { doc, editor, loading } = useEditor(docId, mode, { + blockIds, + elementIds, + }); + + // if sync engine has been synced and the page is null, show 404 page. + if (!doc || !editor) { + return loading ? ( + + ) : ( + + ); + } + + return ( + + + + + + ); +} diff --git a/packages/frontend/core/src/modules/peek-view/view/utils.ts b/packages/frontend/core/src/modules/peek-view/view/utils.ts index e8b8c8804d..dfb7f042cc 100644 --- a/packages/frontend/core/src/modules/peek-view/view/utils.ts +++ b/packages/frontend/core/src/modules/peek-view/view/utils.ts @@ -41,12 +41,9 @@ export const useEditor = ( if (!doc) { return; } - const editor = doc.scope - .get(EditorsService) - .createEditor( - preferModeRef.current || doc.primaryMode$.value, - preferSelectorRef.current - ); + const editor = doc.scope.get(EditorsService).createEditor(); + editor.setMode(preferModeRef.current || doc.primaryMode$.value); + editor.setSelector(preferSelectorRef.current); setEditor(editor); return () => { editor.dispose(); diff --git a/packages/frontend/core/src/modules/workbench/entities/view.ts b/packages/frontend/core/src/modules/workbench/entities/view.ts index 97a597f81d..162df94ae3 100644 --- a/packages/frontend/core/src/modules/workbench/entities/view.ts +++ b/packages/frontend/core/src/modules/workbench/entities/view.ts @@ -86,9 +86,9 @@ export class View extends Entity<{ parseBooleans: true, } ) { - return this.location$.map( - location => queryString.parse(location.search, options) as Partial - ); + return this.location$ + .selector(v => v.search) + .map(search => queryString.parse(search, options) as Partial); } updateQueryString>( diff --git a/packages/frontend/core/src/modules/workbench/index.ts b/packages/frontend/core/src/modules/workbench/index.ts index 84478e89ba..7658140ad4 100644 --- a/packages/frontend/core/src/modules/workbench/index.ts +++ b/packages/frontend/core/src/modules/workbench/index.ts @@ -1,3 +1,4 @@ +export { View as WorkbenchView } from './entities/view'; export { Workbench } from './entities/workbench'; export { ViewScope } from './scopes/view'; export { WorkbenchService } from './services/workbench'; diff --git a/packages/frontend/core/src/pages/workspace/detail-page/detail-page-wrapper.tsx b/packages/frontend/core/src/pages/workspace/detail-page/detail-page-wrapper.tsx index cdb503ade0..640b4db16b 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/detail-page-wrapper.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/detail-page-wrapper.tsx @@ -1,7 +1,6 @@ import type { Editor } from '@affine/core/modules/editor'; import { EditorsService } from '@affine/core/modules/editor'; import { ViewService } from '@affine/core/modules/workbench/services/view'; -import { type DocMode, DocModes } from '@blocksuite/blocks'; import type { Doc } from '@toeverything/infra'; import { DocsService, @@ -26,80 +25,24 @@ const useLoadDoc = (pageId: string) => { const docRecord = useLiveData(docRecordList.doc$(pageId)); const viewService = useService(ViewService); - const queryString$ = viewService.view.queryString$<{ - mode?: string; - blockIds?: string[]; - elementIds?: string[]; - }>({ - // Cannot handle single id situation correctly: `blockIds=xxx` - arrayFormat: 'none', - types: { - mode: 'string', - blockIds: value => (value.length ? value.split(',') : []), - elementIds: value => (value.length ? value.split(',') : []), - }, - }); - - const modeInQuery = useLiveData( - queryString$.map(q => { - if (q.mode && DocModes.includes(q.mode as DocMode)) { - return q.mode as DocMode; - } - return null; - }) - ); - - // We only read the querystring mode when entering, so use useState here. - const [initialQueryStringSelector] = useState(() => { - const { blockIds, elementIds } = queryString$.value; - return { blockIds, elementIds }; - }); - const [doc, setDoc] = useState(null); const [editor, setEditor] = useState(null); - const editorMode = useLiveData(editor?.mode$); useLayoutEffect(() => { if (!docRecord) { return; } - const { doc: opened, release } = docsService.open(pageId); - setDoc(opened); - return () => { - release(); - }; - }, [docRecord, docsService, pageId]); - - useLayoutEffect(() => { - if (!doc) { - return; - } - - const editor = doc.scope - .get(EditorsService) - .createEditor( - modeInQuery || doc.getPrimaryMode() || ('page' as DocMode), - initialQueryStringSelector - ); + const { doc, release } = docsService.open(pageId); + setDoc(doc); + const editor = doc.scope.get(EditorsService).createEditor(); + const unbind = editor.bindWorkbenchView(viewService.view); setEditor(editor); return () => { + unbind(); editor.dispose(); + release(); }; - }, [doc, modeInQuery, initialQueryStringSelector]); - - // update editor mode to queryString - useEffect(() => { - if (editorMode) { - viewService.view.updateQueryString( - { - mode: editorMode, - }, - { - replace: true, - } - ); - } - }, [editorMode, viewService.view]); + }, [docRecord, docsService, pageId, viewService.view]); // set sync engine priority target useEffect(() => { diff --git a/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx index d502ba83e2..0a793c027a 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx @@ -33,7 +33,7 @@ import { WorkspaceService, } from '@toeverything/infra'; import clsx from 'clsx'; -import { memo, useCallback, useEffect, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useRef } from 'react'; import { useParams } from 'react-router-dom'; import type { Map as YMap } from 'yjs'; @@ -93,7 +93,6 @@ const DetailPageImpl = memo(function DetailPageImpl() { const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash)); const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper(); const editorContainer = useLiveData(editor.editorContainer$); - const [defaultSelector] = useState(() => editor.selector$.value); const isSideBarOpen = useLiveData(workbench.sidebarOpen$); const { appSettings } = useAppSettingHelper(); @@ -290,7 +289,6 @@ const DetailPageImpl = memo(function DetailPageImpl() { pageId={doc.id} onLoad={onLoad} docCollection={docCollection} - defaultEditorSelector={defaultSelector} />