Skip to content

Commit c3ae219

Browse files
committed
feat(core): add block and element toolbar widget custom config (#7886)
Upstreams: toeverything/blocksuite#8001 toeverything/blocksuite#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">
1 parent ad11007 commit c3ae219

File tree

7 files changed

+120
-8
lines changed

7 files changed

+120
-8
lines changed

packages/common/infra/src/modules/doc/stores/docs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { DocMode } from '@blocksuite/blocks';
2-
import { type DocMeta } from '@blocksuite/store';
2+
import type { DocMeta } from '@blocksuite/store';
33
import { isEqual } from 'lodash-es';
44
import { distinctUntilChanged, Observable } from 'rxjs';
55

packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
} from '@blocksuite/blocks';
2020
import { type FrameworkProvider } from '@toeverything/infra';
2121

22-
import { createLinkedWidgetConfig } from './linked-widget';
22+
import { createLinkedWidgetConfig } from './widgets/linked';
23+
import { createToolbarMoreMenuConfig } from './widgets/toolbar';
2324

2425
function customLoadFonts(service: RootService): void {
2526
if (runtimeConfig.isSelfHosted) {
@@ -70,6 +71,7 @@ export function createPageRootBlockSpec(
7071
ConfigExtension('affine:page', {
7172
linkedWidget: createLinkedWidgetConfig(framework),
7273
editorSetting: editorSettingService.editorSetting.settingSignal,
74+
toolbarMoreMenu: createToolbarMoreMenuConfig(framework),
7375
}),
7476
];
7577
}
@@ -92,6 +94,7 @@ export function createEdgelessRootBlockSpec(
9294
ConfigExtension('affine:page', {
9395
linkedWidget: createLinkedWidgetConfig(framework),
9496
editorSetting: editorSettingService.editorSetting.settingSignal,
97+
toolbarMoreMenu: createToolbarMoreMenuConfig(framework),
9598
}),
9699
];
97100
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { notify } from '@affine/component';
2+
import {
3+
generateUrl,
4+
type UseSharingUrl,
5+
} from '@affine/core/hooks/affine/use-share-url';
6+
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
7+
import { EditorService } from '@affine/core/modules/editor';
8+
import { I18n } from '@affine/i18n';
9+
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
10+
import type { MenuContext } from '@blocksuite/blocks';
11+
import { LinkIcon } from '@blocksuite/icons/lit';
12+
import type { FrameworkProvider } from '@toeverything/infra';
13+
14+
export function createToolbarMoreMenuConfig(framework: FrameworkProvider) {
15+
return {
16+
configure: <T extends MenuContext>(groups: MenuItemGroup<T>[]) => {
17+
const clipboardGroup = groups.find(group => group.type === 'clipboard');
18+
19+
if (clipboardGroup) {
20+
let copyIndex = clipboardGroup.items.findIndex(
21+
item => item.type === 'copy'
22+
);
23+
if (copyIndex === -1) {
24+
copyIndex = clipboardGroup.items.findIndex(
25+
item => item.type === 'duplicate'
26+
);
27+
if (copyIndex !== -1) {
28+
copyIndex -= 1;
29+
}
30+
}
31+
32+
// after `copy` or before `duplicate`
33+
clipboardGroup.items.splice(
34+
copyIndex + 1,
35+
0,
36+
createCopyLinkToBlockMenuItem(framework)
37+
);
38+
}
39+
40+
return groups;
41+
},
42+
};
43+
}
44+
45+
function createCopyLinkToBlockMenuItem(
46+
framework: FrameworkProvider,
47+
item = {
48+
icon: LinkIcon({ width: '20', height: '20' }),
49+
label: 'Copy link to block',
50+
type: 'copy-link-to-block',
51+
when: (ctx: MenuContext) => {
52+
if (ctx.isEmpty()) return false;
53+
54+
const { editor } = framework.get(EditorService);
55+
const mode = editor.mode$.value;
56+
57+
if (mode === 'edgeless') {
58+
// linking blocks in notes is currently not supported in edgeless mode.
59+
if (ctx.selectedBlockModels.length > 0) {
60+
return false;
61+
}
62+
63+
// linking single block/element in edgeless mode.
64+
if (ctx.isMultiple()) {
65+
return false;
66+
}
67+
}
68+
69+
return true;
70+
},
71+
}
72+
) {
73+
return {
74+
...item,
75+
action: (ctx: MenuContext) => {
76+
const baseUrl = getAffineCloudBaseUrl();
77+
if (!baseUrl) return;
78+
79+
const { editor } = framework.get(EditorService);
80+
const mode = editor.mode$.value;
81+
const pageId = editor.doc.id;
82+
const workspaceId = editor.doc.workspace.id;
83+
const options: UseSharingUrl = { workspaceId, pageId, shareMode: mode };
84+
85+
if (mode === 'page') {
86+
// maybe multiple blocks
87+
const blockIds = ctx.selectedBlockModels.map(model => model.id);
88+
options.blockIds = blockIds;
89+
} else if (mode === 'edgeless' && ctx.firstElement) {
90+
// single block/element
91+
const id = ctx.firstElement.id;
92+
const key = ctx.isElement() ? 'element' : 'block';
93+
options[`${key}Ids`] = [id];
94+
}
95+
96+
const str = generateUrl(options);
97+
if (!str) return;
98+
99+
navigator.clipboard
100+
.writeText(str)
101+
.then(() => {
102+
notify.success({
103+
title: I18n['Copied link to clipboard'](),
104+
});
105+
})
106+
.catch(console.error);
107+
},
108+
};
109+
}

packages/frontend/core/src/hooks/affine/use-share-url.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useCallback } from 'react';
88

99
import { useActiveBlocksuiteEditor } from '../use-block-suite-editor';
1010

11-
type UseSharingUrl = {
11+
export type UseSharingUrl = {
1212
workspaceId: string;
1313
pageId: string;
1414
shareMode?: DocMode;
@@ -20,7 +20,7 @@ type UseSharingUrl = {
2020
/**
2121
* to generate a url like https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode?element=seletedBlockid#seletedBlockid
2222
*/
23-
const generateUrl = ({
23+
export const generateUrl = ({
2424
workspaceId,
2525
pageId,
2626
blockIds,

packages/frontend/core/src/pages/workspace/detail-page/detail-page-wrapper.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
22
import type { Editor } from '@affine/core/modules/editor';
33
import { EditorsService } from '@affine/core/modules/editor';
44
import { ViewService } from '@affine/core/modules/workbench/services/view';
5-
import type { DocMode } from '@blocksuite/blocks';
5+
import { type DocMode, DocModes } from '@blocksuite/blocks';
66
import type { Doc } from '@toeverything/infra';
77
import {
88
DocsService,
@@ -35,7 +35,7 @@ const useLoadDoc = (pageId: string) => {
3535
);
3636

3737
const queryStringMode =
38-
queryString.mode && ['edgeless', 'page'].includes(queryString.mode)
38+
queryString.mode && DocModes.includes(queryString.mode)
3939
? (queryString.mode as DocMode)
4040
: null;
4141

packages/frontend/core/src/pages/workspace/share/share-page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ShareReaderService } from '@affine/core/modules/share-doc';
1212
import { CloudBlobStorage } from '@affine/core/modules/workspace-engine';
1313
import { WorkspaceFlavour } from '@affine/env/workspace';
1414
import { useI18n } from '@affine/i18n';
15-
import type { DocMode } from '@blocksuite/blocks';
15+
import { type DocMode, DocModes } from '@blocksuite/blocks';
1616
import { noop } from '@blocksuite/global/utils';
1717
import { Logo1Icon } from '@blocksuite/icons/rc';
1818
import type { AffineEditorContainer } from '@blocksuite/presets';
@@ -59,7 +59,7 @@ export const SharePage = ({
5959
useEffect(() => {
6060
const searchParams = new URLSearchParams(location.search);
6161
const queryStringMode = searchParams.get('mode') as DocMode | null;
62-
if (queryStringMode && ['edgeless', 'page'].includes(queryStringMode)) {
62+
if (queryStringMode && DocModes.includes(queryStringMode)) {
6363
setMode(queryStringMode);
6464
}
6565
}, [location.search]);

0 commit comments

Comments
 (0)