fundon
2024-09-05 07:47:40 +00:00
parent 19a7d1eb92
commit 059030fbeb
2 changed files with 83 additions and 26 deletions

View File

@@ -2,7 +2,10 @@ import { notify, Skeleton } from '@affine/component';
import { Button } from '@affine/component/ui/button'; import { Button } from '@affine/component/ui/button';
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu'; import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
import { openSettingModalAtom } from '@affine/core/atoms'; 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 { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { ServerConfigService } from '@affine/core/modules/cloud'; import { ServerConfigService } from '@affine/core/modules/cloud';
@@ -26,7 +29,7 @@ import {
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { Suspense, useCallback, useEffect } from 'react'; import { Suspense, useCallback, useEffect, useMemo } from 'react';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import { CloudSvg } from '../cloud-svg'; import { CloudSvg } from '../cloud-svg';
@@ -65,6 +68,8 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
workspaceMetadata: { id: workspaceId }, workspaceMetadata: { id: workspaceId },
} = props; } = props;
const editor = useService(EditorService).editor; const editor = useService(EditorService).editor;
const currentMode = useLiveData(editor.mode$);
const editorContainer = useLiveData(editor.editorContainer$);
const shareInfoService = useService(ShareInfoService); const shareInfoService = useService(ShareInfoService);
const serverConfig = useService(ServerConfigService).serverConfig; const serverConfig = useService(ServerConfigService).serverConfig;
useEffect(() => { useEffect(() => {
@@ -153,6 +158,10 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
const isMac = environment.isMacOs; const isMac = environment.isMacOs;
const { blockIds, elementIds } = useMemo(
() => getSelectedNodes(editorContainer?.host || null, currentMode),
[editorContainer, currentMode]
);
const { onClickCopyLink } = useSharingUrl({ const { onClickCopyLink } = useSharingUrl({
workspaceId, workspaceId,
pageId: editor.doc.id, pageId: editor.doc.id,
@@ -165,9 +174,8 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
onClickCopyLink('edgeless' as DocMode); onClickCopyLink('edgeless' as DocMode);
}, [onClickCopyLink]); }, [onClickCopyLink]);
const onCopyBlockLink = useCallback(() => { const onCopyBlockLink = useCallback(() => {
// TODO(@JimmFly): handle frame onClickCopyLink(currentMode, blockIds, elementIds);
onClickCopyLink(); }, [currentMode, onClickCopyLink, blockIds, elementIds]);
}, [onClickCopyLink]);
if (isLoading) { if (isLoading) {
// TODO(@eyhn): loading and error UI // TODO(@eyhn): loading and error UI
@@ -286,7 +294,11 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
> >
{t['com.affine.share-menu.copy.edgeless']()} {t['com.affine.share-menu.copy.edgeless']()}
</MenuItem> </MenuItem>
<MenuItem prefixIcon={<BlockIcon />} onSelect={onCopyBlockLink}> <MenuItem
prefixIcon={<BlockIcon />}
onSelect={onCopyBlockLink}
disabled={blockIds.length + elementIds.length === 0}
>
{t['com.affine.share-menu.copy.block']()} {t['com.affine.share-menu.copy.block']()}
</MenuItem> </MenuItem>
</> </>

View File

@@ -2,12 +2,11 @@ import { notify } from '@affine/component';
import { track } from '@affine/core/mixpanel'; import { track } from '@affine/core/mixpanel';
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch'; import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import type { BaseSelection } from '@blocksuite/block-std'; import { type EditorHost } from '@blocksuite/block-std';
import type { DocMode } from '@blocksuite/blocks'; import { GfxBlockElementModel } from '@blocksuite/block-std/gfx';
import type { DocMode, EdgelessRootService } from '@blocksuite/blocks';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useActiveBlocksuiteEditor } from '../use-block-suite-editor';
export type UseSharingUrl = { export type UseSharingUrl = {
workspaceId: string; workspaceId: string;
pageId: 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 = ({ export const generateUrl = ({
workspaceId, workspaceId,
@@ -75,29 +76,72 @@ const getShareLinkType = ({
} }
}; };
const getSelectionIds = (selections?: BaseSelection[]) => { export const getSelectedNodes = (
if (!selections || selections.length === 0) { host: EditorHost | null,
return { blockIds: [], elementIds: [] }; mode: DocMode = 'page'
} ) => {
const std = host?.std;
const blockIds: string[] = []; const blockIds: string[] = [];
const elementIds: string[] = []; const elementIds: string[] = [];
// TODO(@JimmFly): handle multiple selections and elementIds const result = { blockIds, elementIds };
if (selections[0].type === 'block') {
blockIds.push(selections[0].blockId); if (!std) {
return result;
} }
return { blockIds, elementIds };
if (mode === 'edgeless') {
const service = std.getService<EdgelessRootService>('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) => { export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
const t = useI18n(); const t = useI18n();
const [editor] = useActiveBlocksuiteEditor();
const onClickCopyLink = useCallback( const onClickCopyLink = useCallback(
(shareMode?: DocMode) => { (shareMode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
const selectManager = editor?.host?.selection;
const selections = selectManager?.value;
const { blockIds, elementIds } = getSelectionIds(selections);
const sharingUrl = generateUrl({ const sharingUrl = generateUrl({
workspaceId, workspaceId,
pageId, pageId,
@@ -130,8 +174,9 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
}); });
} }
}, },
[editor, pageId, t, workspaceId] [pageId, t, workspaceId]
); );
return { return {
onClickCopyLink, onClickCopyLink,
}; };