diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx index 2bc67551d9..1f4ad2ca45 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx @@ -2,7 +2,10 @@ import { notify, Skeleton } from '@affine/component'; import { Button } from '@affine/component/ui/button'; import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu'; import { openSettingModalAtom } from '@affine/core/atoms'; -import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url'; +import { + getSelectedNodes, + useSharingUrl, +} from '@affine/core/hooks/affine/use-share-url'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { track } from '@affine/core/mixpanel'; import { ServerConfigService } from '@affine/core/modules/cloud'; @@ -26,7 +29,7 @@ import { import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { useSetAtom } from 'jotai'; -import { Suspense, useCallback, useEffect } from 'react'; +import { Suspense, useCallback, useEffect, useMemo } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { CloudSvg } from '../cloud-svg'; @@ -65,6 +68,8 @@ export const AFFiNESharePage = (props: ShareMenuProps) => { workspaceMetadata: { id: workspaceId }, } = props; const editor = useService(EditorService).editor; + const currentMode = useLiveData(editor.mode$); + const editorContainer = useLiveData(editor.editorContainer$); const shareInfoService = useService(ShareInfoService); const serverConfig = useService(ServerConfigService).serverConfig; useEffect(() => { @@ -153,6 +158,10 @@ export const AFFiNESharePage = (props: ShareMenuProps) => { const isMac = environment.isMacOs; + const { blockIds, elementIds } = useMemo( + () => getSelectedNodes(editorContainer?.host || null, currentMode), + [editorContainer, currentMode] + ); const { onClickCopyLink } = useSharingUrl({ workspaceId, pageId: editor.doc.id, @@ -165,9 +174,8 @@ export const AFFiNESharePage = (props: ShareMenuProps) => { onClickCopyLink('edgeless' as DocMode); }, [onClickCopyLink]); const onCopyBlockLink = useCallback(() => { - // TODO(@JimmFly): handle frame - onClickCopyLink(); - }, [onClickCopyLink]); + onClickCopyLink(currentMode, blockIds, elementIds); + }, [currentMode, onClickCopyLink, blockIds, elementIds]); if (isLoading) { // TODO(@eyhn): loading and error UI @@ -286,7 +294,11 @@ export const AFFiNESharePage = (props: ShareMenuProps) => { > {t['com.affine.share-menu.copy.edgeless']()} - } onSelect={onCopyBlockLink}> + } + onSelect={onCopyBlockLink} + disabled={blockIds.length + elementIds.length === 0} + > {t['com.affine.share-menu.copy.block']()} diff --git a/packages/frontend/core/src/hooks/affine/use-share-url.ts b/packages/frontend/core/src/hooks/affine/use-share-url.ts index 362e629e7e..1b852bb71a 100644 --- a/packages/frontend/core/src/hooks/affine/use-share-url.ts +++ b/packages/frontend/core/src/hooks/affine/use-share-url.ts @@ -2,12 +2,11 @@ import { notify } from '@affine/component'; import { track } from '@affine/core/mixpanel'; import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch'; import { useI18n } from '@affine/i18n'; -import type { BaseSelection } from '@blocksuite/block-std'; -import type { DocMode } from '@blocksuite/blocks'; +import { type EditorHost } from '@blocksuite/block-std'; +import { GfxBlockElementModel } from '@blocksuite/block-std/gfx'; +import type { DocMode, EdgelessRootService } from '@blocksuite/blocks'; import { useCallback } from 'react'; -import { useActiveBlocksuiteEditor } from '../use-block-suite-editor'; - export type UseSharingUrl = { workspaceId: string; pageId: string; @@ -18,7 +17,9 @@ export type UseSharingUrl = { }; /** - * to generate a url like https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode?element=seletedBlockid#seletedBlockid + * To generate a url like + * + * https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode&elementIds=seletedElementIds&blockIds=selectedBlockIds */ export const generateUrl = ({ workspaceId, @@ -75,29 +76,72 @@ const getShareLinkType = ({ } }; -const getSelectionIds = (selections?: BaseSelection[]) => { - if (!selections || selections.length === 0) { - return { blockIds: [], elementIds: [] }; - } +export const getSelectedNodes = ( + host: EditorHost | null, + mode: DocMode = 'page' +) => { + const std = host?.std; const blockIds: string[] = []; const elementIds: string[] = []; - // TODO(@JimmFly): handle multiple selections and elementIds - if (selections[0].type === 'block') { - blockIds.push(selections[0].blockId); + const result = { blockIds, elementIds }; + + if (!std) { + return result; } - return { blockIds, elementIds }; + + if (mode === 'edgeless') { + const service = std.getService('affine:page'); + if (!service) return result; + + for (const element of service.selection.selectedElements) { + if (element instanceof GfxBlockElementModel) { + blockIds.push(element.id); + } else { + elementIds.push(element.id); + } + } + + return result; + } + + const [success, ctx] = std.command + .chain() + .tryAll(chain => [ + chain.getTextSelection(), + chain.getBlockSelections(), + chain.getImageSelections(), + ]) + .getSelectedModels({ + mode: 'highest', + }) + .run(); + + if (!success) { + return result; + } + + // should return an empty array if `to` of the range is null + if ( + ctx.currentTextSelection && + !ctx.currentTextSelection.to && + ctx.currentTextSelection.from.length === 0 + ) { + return result; + } + + if (ctx.selectedModels?.length) { + blockIds.push(...ctx.selectedModels.map(model => model.id)); + return result; + } + + return result; }; export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => { const t = useI18n(); - const [editor] = useActiveBlocksuiteEditor(); const onClickCopyLink = useCallback( - (shareMode?: DocMode) => { - const selectManager = editor?.host?.selection; - const selections = selectManager?.value; - const { blockIds, elementIds } = getSelectionIds(selections); - + (shareMode?: DocMode, blockIds?: string[], elementIds?: string[]) => { const sharingUrl = generateUrl({ workspaceId, pageId, @@ -130,8 +174,9 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => { }); } }, - [editor, pageId, t, workspaceId] + [pageId, t, workspaceId] ); + return { onClickCopyLink, };