From 0c742eb41b12a1fa21fad8d9d7dd7a8718502615 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Fri, 31 May 2024 16:09:14 +0800 Subject: [PATCH] Update modal-provider.tsx --- .../affine/reference-link/index.tsx | 28 +++- .../modules/peek-view/entities/peek-view.ts | 122 ++++++++++++++-- .../core/src/modules/peek-view/index.ts | 2 +- .../peek-view/view/peek-view-manager.tsx | 131 ++++-------------- .../core/src/modules/peek-view/view/utils.ts | 49 ------- .../modules/workbench/view/workbench-link.tsx | 33 ++--- .../core/src/providers/modal-provider.tsx | 2 +- 7 files changed, 177 insertions(+), 190 deletions(-) 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 29c7e3b37c42b..1d1f010181ca5 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -1,10 +1,12 @@ import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta'; import { useJournalHelper } from '@affine/core/hooks/use-journal'; +import { PeekViewService } from '@affine/core/modules/peek-view'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { LinkedPageIcon, TodayIcon } from '@blocksuite/icons'; import type { DocCollection } from '@blocksuite/store'; -import type { PropsWithChildren } from 'react'; +import { useService } from '@toeverything/infra'; +import { type PropsWithChildren, useCallback, useRef } from 'react'; import * as styles from './styles.css'; @@ -64,8 +66,30 @@ export function AffinePageReference({ t, }); + const ref = useRef(null); + + const peekView = useService(PeekViewService).peekView; + + const onClick = useCallback( + (e: React.MouseEvent) => { + if (e.shiftKey && ref.current) { + e.preventDefault(); + e.stopPropagation(); + peekView.open(ref.current); + return true; // means this click is handled + } + return false; + }, + [peekView] + ); + return ( - + {Wrapper ? {el} : el} ); diff --git a/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts b/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts index 0d73dae9ff486..43d84b8b6c0cd 100644 --- a/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts +++ b/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts @@ -1,16 +1,116 @@ import type { BlockElement } from '@blocksuite/block-std'; -import type { AffineReference } from '@blocksuite/blocks'; -import { Entity, LiveData } from '@toeverything/infra'; +import { + AffineReference, + type EmbedLinkedDocModel, + type EmbedSyncedDocModel, + type SurfaceRefBlockComponent, + type SurfaceRefBlockModel, +} from '@blocksuite/blocks'; +import { type DocMode, Entity, LiveData } from '@toeverything/infra'; + +export type PeekViewTarget = + | HTMLElement + | BlockElement + | AffineReference + | HTMLAnchorElement + | { docId: string; blockId?: string }; + +export type DocPeekViewInfo = { + docId: string; + blockId?: string; + mode?: DocMode; + xywh?: `[${number},${number},${number},${number}]`; +}; export type ActivePeekView = { - target: - | HTMLElement - | BlockElement - | AffineReference - | HTMLAnchorElement - | { docId: string; blockId?: string }; + target: PeekViewTarget; + info: DocPeekViewInfo; +}; + +import type { BlockModel } from '@blocksuite/store'; + +const EMBED_DOC_FLAVOURS = [ + 'affine:embed-linked-doc', + 'affine:embed-synced-doc', +]; + +const isEmbedDocModel = ( + blockModel: BlockModel +): blockModel is EmbedSyncedDocModel | EmbedLinkedDocModel => { + return EMBED_DOC_FLAVOURS.includes(blockModel.flavour); }; +const isSurfaceRefModel = ( + blockModel: BlockModel +): blockModel is SurfaceRefBlockModel => { + return blockModel.flavour === 'affine:surface-ref'; +}; + +const resolveLinkToDoc = (href: string) => { + // http://xxx/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j#xxxx + // to { workspaceId: '48__RTCSwASvWZxyAk3Jw', docId: '-Uge-K6SYcAbcNYfQ5U-j', blockId: 'xxxx' } + + const [_, workspaceId, docId, blockId] = + href.match(/\/workspace\/([^/]+)\/([^#]+)(?:#(.+))?/) || []; + + /** + * @see /packages/frontend/core/src/router.tsx + */ + const excludedPaths = ['all', 'collection', 'tag', 'trash']; + + if (!docId || excludedPaths.includes(docId)) { + return null; + } + + return { workspaceId, docId, blockId }; +}; + +function resolvePeekInfoFromPeekTarget( + peekTarget?: PeekViewTarget +): DocPeekViewInfo | null { + if (!peekTarget) return null; + if (peekTarget instanceof AffineReference) { + if (peekTarget.refMeta) { + return { + docId: peekTarget.refMeta.id, + }; + } + } else if ('model' in peekTarget) { + const blockModel = peekTarget.model; + if (isEmbedDocModel(blockModel)) { + return { + docId: blockModel.pageId, + }; + } else if (isSurfaceRefModel(blockModel)) { + const refModel = (peekTarget as SurfaceRefBlockComponent).referenceModel; + // refModel can be null if the reference is invalid + if (refModel) { + const docId = + 'doc' in refModel ? refModel.doc.id : refModel.surface.doc.id; + return { + docId, + mode: 'edgeless', + xywh: refModel.xywh, + }; + } + } + } else if (peekTarget instanceof HTMLAnchorElement) { + const maybeDoc = resolveLinkToDoc(peekTarget.href); + if (maybeDoc) { + return { + docId: maybeDoc.docId, + blockId: maybeDoc.blockId, + }; + } + } else if ('docId' in peekTarget) { + return { + docId: peekTarget.docId, + blockId: peekTarget.blockId, + }; + } + return null; +} + export class PeekViewEntity extends Entity { constructor() { super(); @@ -25,7 +125,11 @@ export class PeekViewEntity extends Entity { .distinctUntilChanged(); open = (target: ActivePeekView['target']) => { - this._active$.next({ target }); + const resolvedInfo = resolvePeekInfoFromPeekTarget(target); + if (!resolvedInfo) { + return; + } + this._active$.next({ target, info: resolvedInfo }); this._show$.next(true); }; diff --git a/packages/frontend/core/src/modules/peek-view/index.ts b/packages/frontend/core/src/modules/peek-view/index.ts index eb46e5ddba1a4..304159ad78039 100644 --- a/packages/frontend/core/src/modules/peek-view/index.ts +++ b/packages/frontend/core/src/modules/peek-view/index.ts @@ -8,4 +8,4 @@ export function configurePeekViewModule(framework: Framework) { } export { PeekViewEntity, PeekViewService }; -export { useInsidePeekView } from './view'; +export { PeekViewManagerModal,useInsidePeekView } from './view'; diff --git a/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx b/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx index 46b89d8ba5ac2..ef8d026d071b1 100644 --- a/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx @@ -1,8 +1,4 @@ -import { - AffineReference, - type SurfaceRefBlockComponent, -} from '@blocksuite/blocks'; -import { type DocMode, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useMemo } from 'react'; import type { ActivePeekView } from '../entities/peek-view'; @@ -10,123 +6,52 @@ import { PeekViewService } from '../services/peek-view'; import { DocPeekViewControls } from './doc-peek-controls'; import { DocPeekView, SurfaceRefPeekView } from './doc-peek-view'; import { PeekViewModalContainer } from './modal-container'; -import { isEmbedDocModel, isSurfaceRefModel, resolveLinkToDoc } from './utils'; -type DocPeekViewInfo = { - docId: string; - blockId?: string; - mode?: DocMode; - xywh?: `[${number},${number},${number},${number}]`; -}; - -function resolveDocIdFromPeekTarget( - peekTarget?: ActivePeekView['target'] -): DocPeekViewInfo | null { - if (!peekTarget) return null; - if (peekTarget instanceof AffineReference) { - if (peekTarget.refMeta) { - return { - docId: peekTarget.refMeta.id, - }; - } - } else if ('model' in peekTarget) { - const blockModel = peekTarget.model; - if (isEmbedDocModel(blockModel)) { - return { - docId: blockModel.pageId, - }; - } else if (isSurfaceRefModel(blockModel)) { - const refModel = (peekTarget as SurfaceRefBlockComponent).referenceModel; - // refModel can be null if the reference is invalid - if (refModel) { - const docId = - 'doc' in refModel ? refModel.doc.id : refModel.surface.doc.id; - return { - docId, - mode: 'edgeless', - xywh: refModel.xywh, - }; - } - } - } else if (peekTarget instanceof HTMLAnchorElement) { - const maybeDoc = resolveLinkToDoc(peekTarget.href); - if (maybeDoc) { - return { - docId: maybeDoc.docId, - blockId: maybeDoc.blockId, - }; - } - } else if ('docId' in peekTarget) { - return { - docId: peekTarget.docId, - blockId: peekTarget.blockId, - }; - } - return null; -} - -function renderPeekView(peekTarget?: ActivePeekView['target']) { - const docPeekViewInfo = resolveDocIdFromPeekTarget(peekTarget); - - if (docPeekViewInfo) { - if (docPeekViewInfo.mode === 'edgeless' && docPeekViewInfo.xywh) { - return ( - - ); - } - - return ( - - ); +function renderPeekView({ info }: ActivePeekView) { + if (info.mode === 'edgeless' && info.xywh) { + return ; } - return null; + return ( + + ); } -const renderControls = (peekTarget?: ActivePeekView['target']) => { - const docPeekViewInfo = resolveDocIdFromPeekTarget(peekTarget); - - if (docPeekViewInfo) { - return ( - - ); - } - - return null; +const renderControls = ({ info }: ActivePeekView) => { + return ( + + ); }; export const PeekViewManagerModal = () => { - const peekView = useService(PeekViewService).peekView; - const peekTarget = useLiveData(peekView.active$)?.target; - const show = useLiveData(peekView.show$); + const peekViewEntity = useService(PeekViewService).peekView; + const activePeekView = useLiveData(peekViewEntity.active$); + const show = useLiveData(peekViewEntity.show$); const preview = useMemo(() => { - return renderPeekView(peekTarget); - }, [peekTarget]); + return activePeekView ? renderPeekView(activePeekView) : null; + }, [activePeekView]); const controls = useMemo(() => { - return renderControls(peekTarget); - }, [peekTarget]); + return activePeekView ? renderControls(activePeekView) : null; + }, [activePeekView]); return ( { if (!open) { - peekView.close(); + peekViewEntity.close(); } }} > 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 8509880286eda..71c15568e56b7 100644 --- a/packages/frontend/core/src/modules/peek-view/view/utils.ts +++ b/packages/frontend/core/src/modules/peek-view/view/utils.ts @@ -1,10 +1,3 @@ -import type { - EmbedLinkedDocModel, - EmbedSyncedDocModel, - ImageBlockModel, - SurfaceRefBlockModel, -} from '@blocksuite/blocks'; -import type { BlockModel } from '@blocksuite/store'; import type { Doc } from '@toeverything/infra'; import { DocsService, @@ -14,48 +7,6 @@ import { } from '@toeverything/infra'; import { useEffect, useLayoutEffect, useState } from 'react'; -const EMBED_DOC_FLAVOURS = [ - 'affine:embed-linked-doc', - 'affine:embed-synced-doc', -]; - -export const isEmbedDocModel = ( - blockModel: BlockModel -): blockModel is EmbedSyncedDocModel | EmbedLinkedDocModel => { - return EMBED_DOC_FLAVOURS.includes(blockModel.flavour); -}; - -export const isSurfaceRefModel = ( - blockModel: BlockModel -): blockModel is SurfaceRefBlockModel => { - return blockModel.flavour === 'affine:surface-ref'; -}; - -export const isImageModel = ( - blockModel: BlockModel -): blockModel is ImageBlockModel => { - return blockModel.flavour === 'affine:image'; -}; - -export const resolveLinkToDoc = (href: string) => { - // http://xxx/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j#xxxx - // to { workspaceId: '48__RTCSwASvWZxyAk3Jw', docId: '-Uge-K6SYcAbcNYfQ5U-j', blockId: 'xxxx' } - - const [_, workspaceId, docId, blockId] = - href.match(/\/workspace\/([^/]+)\/([^#]+)(?:#(.+))?/) || []; - - /** - * @see /packages/frontend/core/src/router.tsx - */ - const excludedPaths = ['all', 'collection', 'tag', 'trash']; - - if (!docId || excludedPaths.includes(docId)) { - return null; - } - - return { workspaceId, docId, blockId }; -}; - export const useDoc = (pageId: string) => { const currentWorkspace = useService(WorkspaceService).workspace; const docsService = useService(DocsService); diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx index 3935e6d2dedbc..f2e029f72bab6 100644 --- a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx +++ b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx @@ -2,28 +2,20 @@ import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-h import { popupWindow } from '@affine/core/utils'; import { useLiveData, useService } from '@toeverything/infra'; import type { To } from 'history'; -import { useCallback, useRef } from 'react'; +import { forwardRef, useCallback } from 'react'; -import { PeekViewService, useInsidePeekView } from '../../peek-view'; import { WorkbenchService } from '../services/workbench'; -export const WorkbenchLink = ({ - to, - onClick, - ...other -}: React.PropsWithChildren< - { to: To } & React.HTMLProps ->) => { +export const WorkbenchLink = forwardRef< + HTMLAnchorElement, + React.PropsWithChildren<{ to: To } & React.HTMLProps> +>(function WorkbenchLink({ to, onClick, ...other }, ref) { const workbench = useService(WorkbenchService).workbench; - const peekView = useService(PeekViewService).peekView; const { appSettings } = useAppSettingHelper(); const basename = useLiveData(workbench.basename$); const link = basename + (typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`); - const ref = useRef(null); - // if the link is opened in peek view, we should open it in the same peek view - const inPeekView = useInsidePeekView(); const handleClick = useCallback( (event: React.MouseEvent) => { if (onClick?.(event)) { @@ -31,30 +23,21 @@ export const WorkbenchLink = ({ } event.preventDefault(); event.stopPropagation(); + if (event.ctrlKey || event.metaKey) { if (appSettings.enableMultiView && environment.isDesktop) { workbench.open(to, { at: 'beside' }); } else if (!environment.isDesktop) { popupWindow(link); } - } else if ((event.shiftKey || inPeekView) && ref.current) { - peekView.open(ref.current); } else { workbench.open(to); } }, - [ - to, - appSettings.enableMultiView, - inPeekView, - link, - onClick, - peekView, - workbench, - ] + [appSettings.enableMultiView, link, onClick, to, workbench] ); // eslint suspicious runtime error // eslint-disable-next-line react/no-danger-with-children return ; -}; +}); diff --git a/packages/frontend/core/src/providers/modal-provider.tsx b/packages/frontend/core/src/providers/modal-provider.tsx index b428e9a113cb4..4c0f2c2cd94bd 100644 --- a/packages/frontend/core/src/providers/modal-provider.tsx +++ b/packages/frontend/core/src/providers/modal-provider.tsx @@ -24,7 +24,7 @@ import { PaymentDisableModal } from '../components/affine/payment-disable'; import { useAsyncCallback } from '../hooks/affine-async-hooks'; import { useNavigateHelper } from '../hooks/use-navigate-helper'; import { AuthService } from '../modules/cloud/services/auth'; -import { PeekViewManagerModal } from '../modules/peek-view/view'; +import { PeekViewManagerModal } from '../modules/peek-view'; import { WorkspaceSubPath } from '../shared'; const SettingModal = lazy(() =>