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

<img width="376" alt="Screenshot 2024-08-16 at 16 20 08" src="https://github.com/user-attachments/assets/49b41de9-39d1-4f55-ac9b-445fe020187a">
This commit is contained in:
fundon
2024-09-03 03:52:08 +00:00
parent ad110078ac
commit c3ae219992
7 changed files with 120 additions and 8 deletions

View File

@@ -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';

View File

@@ -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),
}),
];
}

View File

@@ -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: <T extends MenuContext>(groups: MenuItemGroup<T>[]) => {
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);
},
};
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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]);