From c3ae21999210c1fec953f923769d0aabfe8ce431 Mon Sep 17 00:00:00 2001 From: fundon Date: Tue, 3 Sep 2024 03:52:08 +0000 Subject: [PATCH] feat(core): add block and element toolbar widget custom config (#7886) Upstreams: https://github.com/toeverything/blocksuite/pull/8001 https://github.com/toeverything/blocksuite/pull/7964 * add block/element toolbar widget config * add `Copy link to block` to `more menu` on block/element toolbar Screenshot 2024-08-16 at 16 20 08 --- .../infra/src/modules/doc/stores/docs.ts | 2 +- .../specs/custom/root-block.ts | 5 +- .../{linked-widget.ts => widgets/linked.ts} | 0 .../specs/custom/widgets/toolbar.ts | 109 ++++++++++++++++++ .../core/src/hooks/affine/use-share-url.ts | 4 +- .../detail-page/detail-page-wrapper.tsx | 4 +- .../src/pages/workspace/share/share-page.tsx | 4 +- 7 files changed, 120 insertions(+), 8 deletions(-) rename packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/{linked-widget.ts => widgets/linked.ts} (100%) create mode 100644 packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/toolbar.ts diff --git a/packages/common/infra/src/modules/doc/stores/docs.ts b/packages/common/infra/src/modules/doc/stores/docs.ts index f7475a3f9..272a589aa 100644 --- a/packages/common/infra/src/modules/doc/stores/docs.ts +++ b/packages/common/infra/src/modules/doc/stores/docs.ts @@ -1,5 +1,5 @@ import type { DocMode } from '@blocksuite/blocks'; -import { type DocMeta } from '@blocksuite/store'; +import type { DocMeta } from '@blocksuite/store'; import { isEqual } from 'lodash-es'; import { distinctUntilChanged, Observable } from 'rxjs'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts index 6b5e12563..9c5cc59fb 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts @@ -19,7 +19,8 @@ import { } from '@blocksuite/blocks'; import { type FrameworkProvider } from '@toeverything/infra'; -import { createLinkedWidgetConfig } from './linked-widget'; +import { createLinkedWidgetConfig } from './widgets/linked'; +import { createToolbarMoreMenuConfig } from './widgets/toolbar'; function customLoadFonts(service: RootService): void { if (runtimeConfig.isSelfHosted) { @@ -70,6 +71,7 @@ export function createPageRootBlockSpec( ConfigExtension('affine:page', { linkedWidget: createLinkedWidgetConfig(framework), editorSetting: editorSettingService.editorSetting.settingSignal, + toolbarMoreMenu: createToolbarMoreMenuConfig(framework), }), ]; } @@ -92,6 +94,7 @@ export function createEdgelessRootBlockSpec( ConfigExtension('affine:page', { linkedWidget: createLinkedWidgetConfig(framework), editorSetting: editorSettingService.editorSetting.settingSignal, + toolbarMoreMenu: createToolbarMoreMenuConfig(framework), }), ]; } diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/linked.ts similarity index 100% rename from packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts rename to packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/linked.ts diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/toolbar.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/toolbar.ts new file mode 100644 index 000000000..7870d9b4d --- /dev/null +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/toolbar.ts @@ -0,0 +1,109 @@ +import { notify } from '@affine/component'; +import { + generateUrl, + type UseSharingUrl, +} from '@affine/core/hooks/affine/use-share-url'; +import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch'; +import { EditorService } from '@affine/core/modules/editor'; +import { I18n } from '@affine/i18n'; +import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar'; +import type { MenuContext } from '@blocksuite/blocks'; +import { LinkIcon } from '@blocksuite/icons/lit'; +import type { FrameworkProvider } from '@toeverything/infra'; + +export function createToolbarMoreMenuConfig(framework: FrameworkProvider) { + return { + configure: (groups: MenuItemGroup[]) => { + const clipboardGroup = groups.find(group => group.type === 'clipboard'); + + if (clipboardGroup) { + let copyIndex = clipboardGroup.items.findIndex( + item => item.type === 'copy' + ); + if (copyIndex === -1) { + copyIndex = clipboardGroup.items.findIndex( + item => item.type === 'duplicate' + ); + if (copyIndex !== -1) { + copyIndex -= 1; + } + } + + // after `copy` or before `duplicate` + clipboardGroup.items.splice( + copyIndex + 1, + 0, + createCopyLinkToBlockMenuItem(framework) + ); + } + + return groups; + }, + }; +} + +function createCopyLinkToBlockMenuItem( + framework: FrameworkProvider, + item = { + icon: LinkIcon({ width: '20', height: '20' }), + label: 'Copy link to block', + type: 'copy-link-to-block', + when: (ctx: MenuContext) => { + if (ctx.isEmpty()) return false; + + const { editor } = framework.get(EditorService); + const mode = editor.mode$.value; + + if (mode === 'edgeless') { + // linking blocks in notes is currently not supported in edgeless mode. + if (ctx.selectedBlockModels.length > 0) { + return false; + } + + // linking single block/element in edgeless mode. + if (ctx.isMultiple()) { + return false; + } + } + + return true; + }, + } +) { + return { + ...item, + action: (ctx: MenuContext) => { + const baseUrl = getAffineCloudBaseUrl(); + if (!baseUrl) return; + + const { editor } = framework.get(EditorService); + const mode = editor.mode$.value; + const pageId = editor.doc.id; + const workspaceId = editor.doc.workspace.id; + const options: UseSharingUrl = { workspaceId, pageId, shareMode: mode }; + + if (mode === 'page') { + // maybe multiple blocks + const blockIds = ctx.selectedBlockModels.map(model => model.id); + options.blockIds = blockIds; + } else if (mode === 'edgeless' && ctx.firstElement) { + // single block/element + const id = ctx.firstElement.id; + const key = ctx.isElement() ? 'element' : 'block'; + options[`${key}Ids`] = [id]; + } + + const str = generateUrl(options); + if (!str) return; + + navigator.clipboard + .writeText(str) + .then(() => { + notify.success({ + title: I18n['Copied link to clipboard'](), + }); + }) + .catch(console.error); + }, + }; +} 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 c7d35ac6e..362e629e7 100644 --- a/packages/frontend/core/src/hooks/affine/use-share-url.ts +++ b/packages/frontend/core/src/hooks/affine/use-share-url.ts @@ -8,7 +8,7 @@ import { useCallback } from 'react'; import { useActiveBlocksuiteEditor } from '../use-block-suite-editor'; -type UseSharingUrl = { +export type UseSharingUrl = { workspaceId: string; pageId: string; shareMode?: DocMode; @@ -20,7 +20,7 @@ type UseSharingUrl = { /** * to generate a url like https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode?element=seletedBlockid#seletedBlockid */ -const generateUrl = ({ +export const generateUrl = ({ workspaceId, pageId, blockIds, 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 917aa0201..537102194 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 @@ -2,7 +2,7 @@ import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton'; 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 } from '@blocksuite/blocks'; +import { type DocMode, DocModes } from '@blocksuite/blocks'; import type { Doc } from '@toeverything/infra'; import { DocsService, @@ -35,7 +35,7 @@ const useLoadDoc = (pageId: string) => { ); const queryStringMode = - queryString.mode && ['edgeless', 'page'].includes(queryString.mode) + queryString.mode && DocModes.includes(queryString.mode) ? (queryString.mode as DocMode) : null; diff --git a/packages/frontend/core/src/pages/workspace/share/share-page.tsx b/packages/frontend/core/src/pages/workspace/share/share-page.tsx index 6a6ea6f58..bb68a637f 100644 --- a/packages/frontend/core/src/pages/workspace/share/share-page.tsx +++ b/packages/frontend/core/src/pages/workspace/share/share-page.tsx @@ -12,7 +12,7 @@ import { ShareReaderService } from '@affine/core/modules/share-doc'; import { CloudBlobStorage } from '@affine/core/modules/workspace-engine'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; -import type { DocMode } from '@blocksuite/blocks'; +import { type DocMode, DocModes } from '@blocksuite/blocks'; import { noop } from '@blocksuite/global/utils'; import { Logo1Icon } from '@blocksuite/icons/rc'; import type { AffineEditorContainer } from '@blocksuite/presets'; @@ -59,7 +59,7 @@ export const SharePage = ({ useEffect(() => { const searchParams = new URLSearchParams(location.search); const queryStringMode = searchParams.get('mode') as DocMode | null; - if (queryStringMode && ['edgeless', 'page'].includes(queryStringMode)) { + if (queryStringMode && DocModes.includes(queryStringMode)) { setMode(queryStringMode); } }, [location.search]);