mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 10:22:55 +08:00
refactor(editor): edgeless element toolbar with new pattern (#10511)
This commit is contained in:
@@ -136,7 +136,6 @@ export const builtinToolbarConfig = {
|
|||||||
id: 'a.rename',
|
id: 'a.rename',
|
||||||
content(cx) {
|
content(cx) {
|
||||||
const component = cx.getCurrentBlockComponentBy(
|
const component = cx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
AttachmentBlockComponent
|
AttachmentBlockComponent
|
||||||
);
|
);
|
||||||
if (!component) return null;
|
if (!component) return null;
|
||||||
@@ -178,7 +177,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: DownloadIcon(),
|
icon: DownloadIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
AttachmentBlockComponent
|
AttachmentBlockComponent
|
||||||
);
|
);
|
||||||
component?.download();
|
component?.download();
|
||||||
@@ -190,7 +188,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: CaptionIcon(),
|
icon: CaptionIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
AttachmentBlockComponent
|
AttachmentBlockComponent
|
||||||
);
|
);
|
||||||
component?.captionEditor?.show();
|
component?.captionEditor?.show();
|
||||||
@@ -212,7 +209,6 @@ export const builtinToolbarConfig = {
|
|||||||
run(ctx) {
|
run(ctx) {
|
||||||
// TODO(@fundon): unify `clone` method
|
// TODO(@fundon): unify `clone` method
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
AttachmentBlockComponent
|
AttachmentBlockComponent
|
||||||
);
|
);
|
||||||
component?.copy();
|
component?.copy();
|
||||||
@@ -224,7 +220,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: DuplicateIcon(),
|
icon: DuplicateIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockComponentBy(
|
const model = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
AttachmentBlockComponent
|
AttachmentBlockComponent
|
||||||
)?.model;
|
)?.model;
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
@@ -247,7 +242,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: ResetIcon(),
|
icon: ResetIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
AttachmentBlockComponent
|
AttachmentBlockComponent
|
||||||
);
|
);
|
||||||
component?.refreshData();
|
component?.refreshData();
|
||||||
@@ -260,7 +254,7 @@ export const builtinToolbarConfig = {
|
|||||||
icon: DeleteIcon(),
|
icon: DeleteIcon(),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
ctx.store.deleteBlock(model);
|
ctx.store.deleteBlock(model);
|
||||||
|
|||||||
@@ -276,7 +276,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: CaptionIcon(),
|
icon: CaptionIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
BookmarkBlockComponent
|
BookmarkBlockComponent
|
||||||
);
|
);
|
||||||
component?.captionEditor?.show();
|
component?.captionEditor?.show();
|
||||||
@@ -296,7 +295,7 @@ export const builtinToolbarConfig = {
|
|||||||
label: 'Copy',
|
label: 'Copy',
|
||||||
icon: CopyIcon(),
|
icon: CopyIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
const slice = Slice.fromModels(ctx.store, [model]);
|
const slice = Slice.fromModels(ctx.store, [model]);
|
||||||
@@ -311,7 +310,7 @@ export const builtinToolbarConfig = {
|
|||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
icon: DuplicateIcon(),
|
icon: DuplicateIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
const { flavour, parent } = model;
|
const { flavour, parent } = model;
|
||||||
@@ -330,7 +329,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: ResetIcon(),
|
icon: ResetIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
BookmarkBlockComponent
|
BookmarkBlockComponent
|
||||||
);
|
);
|
||||||
component?.refreshData();
|
component?.refreshData();
|
||||||
@@ -343,7 +341,7 @@ export const builtinToolbarConfig = {
|
|||||||
icon: DeleteIcon(),
|
icon: DeleteIcon(),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
ctx.store.deleteBlock(model);
|
ctx.store.deleteBlock(model);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
{
|
{
|
||||||
id: 'a.preview',
|
id: 'a.preview',
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return null;
|
if (!model || !isExternalEmbedModel(model)) return null;
|
||||||
|
|
||||||
const { url } = model.props;
|
const { url } = model.props;
|
||||||
@@ -72,7 +72,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
id: 'inline',
|
id: 'inline',
|
||||||
label: 'Inline view',
|
label: 'Inline view',
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return;
|
if (!model || !isExternalEmbedModel(model)) return;
|
||||||
|
|
||||||
const { title, caption, url: link } = model.props;
|
const { title, caption, url: link } = model.props;
|
||||||
@@ -105,7 +105,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
id: 'card',
|
id: 'card',
|
||||||
label: 'Card view',
|
label: 'Card view',
|
||||||
disabled(ctx) {
|
disabled(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return true;
|
if (!model || !isExternalEmbedModel(model)) return true;
|
||||||
|
|
||||||
const { url } = model.props;
|
const { url } = model.props;
|
||||||
@@ -116,7 +116,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
return options?.viewType === 'card';
|
return options?.viewType === 'card';
|
||||||
},
|
},
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return;
|
if (!model || !isExternalEmbedModel(model)) return;
|
||||||
|
|
||||||
const { url, caption } = model.props;
|
const { url, caption } = model.props;
|
||||||
@@ -165,7 +165,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
id: 'embed',
|
id: 'embed',
|
||||||
label: 'Embed view',
|
label: 'Embed view',
|
||||||
disabled(ctx) {
|
disabled(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return false;
|
if (!model || !isExternalEmbedModel(model)) return false;
|
||||||
|
|
||||||
const { url } = model.props;
|
const { url } = model.props;
|
||||||
@@ -176,7 +176,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
return options?.viewType === 'embed';
|
return options?.viewType === 'embed';
|
||||||
},
|
},
|
||||||
when(ctx) {
|
when(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return false;
|
if (!model || !isExternalEmbedModel(model)) return false;
|
||||||
|
|
||||||
const { url } = model.props;
|
const { url } = model.props;
|
||||||
@@ -187,7 +187,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
return options?.viewType === 'embed';
|
return options?.viewType === 'embed';
|
||||||
},
|
},
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return;
|
if (!model || !isExternalEmbedModel(model)) return;
|
||||||
|
|
||||||
const { url, caption } = model.props;
|
const { url, caption } = model.props;
|
||||||
@@ -231,7 +231,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const model = ctx.getCurrentBlockBy(BlockSelection)?.model;
|
const model = ctx.getCurrentModel();
|
||||||
if (!model || !isExternalEmbedModel(model)) return null;
|
if (!model || !isExternalEmbedModel(model)) return null;
|
||||||
|
|
||||||
const { url } = model.props;
|
const { url } = model.props;
|
||||||
@@ -322,10 +322,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
tooltip: 'Caption',
|
tooltip: 'Caption',
|
||||||
icon: CaptionIcon(),
|
icon: CaptionIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(klass);
|
||||||
BlockSelection,
|
|
||||||
klass
|
|
||||||
);
|
|
||||||
if (!component) return;
|
if (!component) return;
|
||||||
|
|
||||||
component.captionEditor?.show();
|
component.captionEditor?.show();
|
||||||
@@ -378,10 +375,7 @@ export function createBuiltinToolbarConfigForExternal(
|
|||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
icon: ResetIcon(),
|
icon: ResetIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(klass);
|
||||||
BlockSelection,
|
|
||||||
klass
|
|
||||||
);
|
|
||||||
component?.refreshData();
|
component?.refreshData();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const builtinToolbarConfig = {
|
|||||||
tooltip: 'Open this doc',
|
tooltip: 'Open this doc',
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedHtmlBlockComponent
|
EmbedHtmlBlockComponent
|
||||||
);
|
);
|
||||||
component?.open();
|
component?.open();
|
||||||
@@ -99,7 +98,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: CaptionIcon(),
|
icon: CaptionIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedHtmlBlockComponent
|
EmbedHtmlBlockComponent
|
||||||
);
|
);
|
||||||
component?.captionEditor?.show();
|
component?.captionEditor?.show();
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ import * as Y from 'yjs';
|
|||||||
import { EmbedIframeBlockComponent } from '../embed-iframe-block';
|
import { EmbedIframeBlockComponent } from '../embed-iframe-block';
|
||||||
|
|
||||||
const trackBaseProps = {
|
const trackBaseProps = {
|
||||||
segment: 'doc',
|
|
||||||
page: 'doc editor',
|
|
||||||
module: 'toolbar',
|
|
||||||
category: 'bookmark',
|
category: 'bookmark',
|
||||||
type: 'card view',
|
type: 'card view',
|
||||||
};
|
};
|
||||||
@@ -156,7 +153,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: CaptionIcon(),
|
icon: CaptionIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedIframeBlockComponent
|
EmbedIframeBlockComponent
|
||||||
);
|
);
|
||||||
component?.captionEditor?.show();
|
component?.captionEditor?.show();
|
||||||
@@ -210,7 +206,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: ResetIcon(),
|
icon: ResetIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedIframeBlockComponent
|
EmbedIframeBlockComponent
|
||||||
);
|
);
|
||||||
component?.refreshData().catch(console.error);
|
component?.refreshData().catch(console.error);
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export const builtinToolbarConfig = {
|
|||||||
id: 'a.doc-title',
|
id: 'a.doc-title',
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedLinkedDocBlockComponent
|
EmbedLinkedDocBlockComponent
|
||||||
);
|
);
|
||||||
if (!component) return null;
|
if (!component) return null;
|
||||||
@@ -63,7 +62,6 @@ export const builtinToolbarConfig = {
|
|||||||
label: 'Inline view',
|
label: 'Inline view',
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedLinkedDocBlockComponent
|
EmbedLinkedDocBlockComponent
|
||||||
);
|
);
|
||||||
component?.covertToInline();
|
component?.covertToInline();
|
||||||
@@ -89,7 +87,6 @@ export const builtinToolbarConfig = {
|
|||||||
label: 'Embed view',
|
label: 'Embed view',
|
||||||
disabled(ctx) {
|
disabled(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedLinkedDocBlockComponent
|
EmbedLinkedDocBlockComponent
|
||||||
);
|
);
|
||||||
if (!component) return true;
|
if (!component) return true;
|
||||||
@@ -108,7 +105,6 @@ export const builtinToolbarConfig = {
|
|||||||
},
|
},
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedLinkedDocBlockComponent
|
EmbedLinkedDocBlockComponent
|
||||||
);
|
);
|
||||||
component?.convertToEmbed();
|
component?.convertToEmbed();
|
||||||
@@ -208,7 +204,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: CaptionIcon(),
|
icon: CaptionIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedLinkedDocBlockComponent
|
EmbedLinkedDocBlockComponent
|
||||||
);
|
);
|
||||||
component?.captionEditor?.show();
|
component?.captionEditor?.show();
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export const builtinToolbarConfig = {
|
|||||||
],
|
],
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedSyncedDocBlockComponent
|
EmbedSyncedDocBlockComponent
|
||||||
);
|
);
|
||||||
if (!component) return null;
|
if (!component) return null;
|
||||||
@@ -117,14 +116,13 @@ export const builtinToolbarConfig = {
|
|||||||
label: 'Inline view',
|
label: 'Inline view',
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedSyncedDocBlockComponent
|
EmbedSyncedDocBlockComponent
|
||||||
);
|
);
|
||||||
component?.covertToInline();
|
component?.covertToInline();
|
||||||
|
|
||||||
// Clears
|
// Clears
|
||||||
ctx.reset();
|
|
||||||
ctx.select('note');
|
ctx.select('note');
|
||||||
|
ctx.reset();
|
||||||
|
|
||||||
ctx.track('SelectedView', {
|
ctx.track('SelectedView', {
|
||||||
...trackBaseProps,
|
...trackBaseProps,
|
||||||
@@ -138,7 +136,6 @@ export const builtinToolbarConfig = {
|
|||||||
label: 'Card view',
|
label: 'Card view',
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedSyncedDocBlockComponent
|
EmbedSyncedDocBlockComponent
|
||||||
);
|
);
|
||||||
component?.convertToCard();
|
component?.convertToCard();
|
||||||
@@ -192,7 +189,6 @@ export const builtinToolbarConfig = {
|
|||||||
icon: CaptionIcon(),
|
icon: CaptionIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedSyncedDocBlockComponent
|
EmbedSyncedDocBlockComponent
|
||||||
);
|
);
|
||||||
component?.captionEditor?.show();
|
component?.captionEditor?.show();
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinAttachmentToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Attachment',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinBookmarkToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Bookmark',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinBrushToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Brush',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
import {
|
||||||
|
AddTextIcon,
|
||||||
|
ConnectorCIcon,
|
||||||
|
FlipDirectionIcon,
|
||||||
|
StartPointIcon,
|
||||||
|
} from '@blocksuite/icons/lit';
|
||||||
|
|
||||||
|
export const builtinConnectorToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.stroke-color',
|
||||||
|
tooltip: 'Stroke style',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'b.style',
|
||||||
|
tooltip: 'Style',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c.start-point-style',
|
||||||
|
icon: StartPointIcon(),
|
||||||
|
tooltip: 'Start point style',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'd.flip-direction',
|
||||||
|
icon: FlipDirectionIcon(),
|
||||||
|
tooltip: 'Flip direction',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e.end-point-style',
|
||||||
|
icon: StartPointIcon(),
|
||||||
|
tooltip: 'End point style',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'f.connector-shape',
|
||||||
|
icon: ConnectorCIcon(),
|
||||||
|
tooltip: 'Connector shape',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'g.add-text',
|
||||||
|
icon: AddTextIcon(),
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinEmbedToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Embed',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinFrameToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Frame',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinGroupToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Group',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinImageToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Image',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
|
||||||
|
import { BlockFlavourIdentifier } from '@blocksuite/block-std';
|
||||||
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { builtinAttachmentToolbarConfig } from './attachment';
|
||||||
|
import { builtinBookmarkToolbarConfig } from './bookmark';
|
||||||
|
import { builtinBrushToolbarConfig } from './brush';
|
||||||
|
import { builtinConnectorToolbarConfig } from './connector';
|
||||||
|
import { builtinEmbedToolbarConfig } from './embed';
|
||||||
|
import { builtinFrameToolbarConfig } from './frame';
|
||||||
|
import { builtinGroupToolbarConfig } from './group';
|
||||||
|
import { builtinImageToolbarConfig } from './image';
|
||||||
|
import { builtinMindmapToolbarConfig } from './mindmap';
|
||||||
|
import { builtinMiscToolbarConfig } from './misc';
|
||||||
|
import { builtinNoteToolbarConfig } from './note';
|
||||||
|
import { builtinShapeToolbarConfig } from './shape';
|
||||||
|
import { builtinTextToolbarConfig } from './text';
|
||||||
|
|
||||||
|
export const EdgelessElementToolbarExtension: ExtensionType[] = [
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:attachment'),
|
||||||
|
config: builtinAttachmentToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:bookmark'),
|
||||||
|
config: builtinBookmarkToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:image'),
|
||||||
|
config: builtinImageToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:brush'),
|
||||||
|
config: builtinBrushToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:connector'),
|
||||||
|
config: builtinConnectorToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:embed'),
|
||||||
|
config: builtinEmbedToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:frame'),
|
||||||
|
config: builtinFrameToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:group'),
|
||||||
|
config: builtinGroupToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:mindmap'),
|
||||||
|
config: builtinMindmapToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:note'),
|
||||||
|
config: builtinNoteToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:shape'),
|
||||||
|
config: builtinShapeToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:text'),
|
||||||
|
config: builtinTextToolbarConfig,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ToolbarModuleExtension({
|
||||||
|
id: BlockFlavourIdentifier('affine:surface:*'),
|
||||||
|
config: builtinMiscToolbarConfig,
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinMindmapToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Mindmap',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
ActionPlacement,
|
||||||
|
type ToolbarModuleConfig,
|
||||||
|
} from '@blocksuite/affine-shared/services';
|
||||||
|
import {
|
||||||
|
ConnectorCIcon,
|
||||||
|
LockIcon,
|
||||||
|
ReleaseFromGroupIcon,
|
||||||
|
} from '@blocksuite/icons/lit';
|
||||||
|
|
||||||
|
export const builtinMiscToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
placement: ActionPlacement.Start,
|
||||||
|
id: 'a.release-from-group',
|
||||||
|
tooltip: 'Release from group',
|
||||||
|
icon: ReleaseFromGroupIcon(),
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
placement: ActionPlacement.Start,
|
||||||
|
id: 'a.misc',
|
||||||
|
label: 'Misc',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
placement: ActionPlacement.End,
|
||||||
|
id: 'a.draw-connector',
|
||||||
|
icon: ConnectorCIcon(),
|
||||||
|
tooltip: 'Draw connector',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
placement: ActionPlacement.End,
|
||||||
|
id: 'b.lock',
|
||||||
|
icon: LockIcon(),
|
||||||
|
tooltip: 'Lock',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinNoteToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Note',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
import {
|
||||||
|
AddTextIcon,
|
||||||
|
ShapeIcon,
|
||||||
|
StyleGeneralIcon,
|
||||||
|
} from '@blocksuite/icons/lit';
|
||||||
|
|
||||||
|
export const builtinShapeToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.switch-type',
|
||||||
|
icon: ShapeIcon(),
|
||||||
|
tooltip: 'Switch type',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'b.style',
|
||||||
|
icon: StyleGeneralIcon(),
|
||||||
|
tooltip: 'Style',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c.fill-color',
|
||||||
|
label: 'Fill color',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'd.border-style',
|
||||||
|
label: 'Border style',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e.text',
|
||||||
|
icon: AddTextIcon(),
|
||||||
|
tooltip: 'Show add button or text menu',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||||
|
|
||||||
|
export const builtinTextToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.test',
|
||||||
|
label: 'Text',
|
||||||
|
run() {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const satisfies ToolbarModuleConfig;
|
||||||
@@ -3,6 +3,7 @@ import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
|
|||||||
import { TextTool } from '@blocksuite/affine-gfx-text';
|
import { TextTool } from '@blocksuite/affine-gfx-text';
|
||||||
import type { ExtensionType } from '@blocksuite/store';
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { EdgelessElementToolbarExtension } from './configs/toolbar';
|
||||||
import { EdgelessRootBlockSpec } from './edgeless-root-spec.js';
|
import { EdgelessRootBlockSpec } from './edgeless-root-spec.js';
|
||||||
import { BrushTool } from './gfx-tool/brush-tool.js';
|
import { BrushTool } from './gfx-tool/brush-tool.js';
|
||||||
import { ConnectorTool } from './gfx-tool/connector-tool.js';
|
import { ConnectorTool } from './gfx-tool/connector-tool.js';
|
||||||
@@ -40,7 +41,8 @@ export const EdgelessBuiltInManager: ExtensionType[] = [
|
|||||||
MindMapIndicatorOverlay,
|
MindMapIndicatorOverlay,
|
||||||
SnapManager,
|
SnapManager,
|
||||||
EditPropsMiddlewareBuilder,
|
EditPropsMiddlewareBuilder,
|
||||||
];
|
EdgelessElementToolbarExtension,
|
||||||
|
].flat();
|
||||||
|
|
||||||
export const EdgelessBuiltInSpecs: ExtensionType[] = [
|
export const EdgelessBuiltInSpecs: ExtensionType[] = [
|
||||||
EdgelessRootBlockSpec,
|
EdgelessRootBlockSpec,
|
||||||
|
|||||||
@@ -27,13 +27,6 @@ export function mountShapeTextEditor(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shapeElement.text) {
|
|
||||||
const text = new Y.Text();
|
|
||||||
edgeless.std
|
|
||||||
.get(EdgelessCRUDIdentifier)
|
|
||||||
.updateElement(shapeElement.id, { text });
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedElement = edgeless.service.crud.getElementById(shapeElement.id);
|
const updatedElement = edgeless.service.crud.getElementById(shapeElement.id);
|
||||||
|
|
||||||
if (!(updatedElement instanceof ShapeElementModel)) {
|
if (!(updatedElement instanceof ShapeElementModel)) {
|
||||||
@@ -41,17 +34,25 @@ export function mountShapeTextEditor(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
edgeless.gfx.tool.setTool('default');
|
||||||
|
edgeless.gfx.selection.set({
|
||||||
|
elements: [shapeElement.id],
|
||||||
|
editing: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shapeElement.text) {
|
||||||
|
const text = new Y.Text();
|
||||||
|
edgeless.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(shapeElement.id, { text });
|
||||||
|
}
|
||||||
|
|
||||||
const shapeEditor = new EdgelessShapeTextEditor();
|
const shapeEditor = new EdgelessShapeTextEditor();
|
||||||
shapeEditor.element = updatedElement;
|
shapeEditor.element = updatedElement;
|
||||||
shapeEditor.edgeless = edgeless;
|
shapeEditor.edgeless = edgeless;
|
||||||
shapeEditor.mountEditor = mountShapeTextEditor;
|
shapeEditor.mountEditor = mountShapeTextEditor;
|
||||||
|
|
||||||
edgeless.mountElm.append(shapeEditor);
|
edgeless.mountElm.append(shapeEditor);
|
||||||
edgeless.gfx.tool.setTool('default');
|
|
||||||
edgeless.gfx.selection.set({
|
|
||||||
elements: [shapeElement.id],
|
|
||||||
editing: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mountFrameTitleEditor(
|
export function mountFrameTitleEditor(
|
||||||
@@ -65,16 +66,17 @@ export function mountFrameTitleEditor(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const frameEditor = new EdgelessFrameTitleEditor();
|
|
||||||
frameEditor.frameModel = frame;
|
|
||||||
frameEditor.edgeless = edgeless;
|
|
||||||
|
|
||||||
edgeless.mountElm.append(frameEditor);
|
|
||||||
edgeless.gfx.tool.setTool('default');
|
edgeless.gfx.tool.setTool('default');
|
||||||
edgeless.gfx.selection.set({
|
edgeless.gfx.selection.set({
|
||||||
elements: [frame.id],
|
elements: [frame.id],
|
||||||
editing: true,
|
editing: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const frameEditor = new EdgelessFrameTitleEditor();
|
||||||
|
frameEditor.frameModel = frame;
|
||||||
|
frameEditor.edgeless = edgeless;
|
||||||
|
|
||||||
|
edgeless.mountElm.append(frameEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mountGroupTitleEditor(
|
export function mountGroupTitleEditor(
|
||||||
@@ -88,16 +90,17 @@ export function mountGroupTitleEditor(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupEditor = new EdgelessGroupTitleEditor();
|
|
||||||
groupEditor.group = group;
|
|
||||||
groupEditor.edgeless = edgeless;
|
|
||||||
|
|
||||||
edgeless.mountElm.append(groupEditor);
|
|
||||||
edgeless.gfx.tool.setTool('default');
|
edgeless.gfx.tool.setTool('default');
|
||||||
edgeless.gfx.selection.set({
|
edgeless.gfx.selection.set({
|
||||||
elements: [group.id],
|
elements: [group.id],
|
||||||
editing: true,
|
editing: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const groupEditor = new EdgelessGroupTitleEditor();
|
||||||
|
groupEditor.group = group;
|
||||||
|
groupEditor.edgeless = edgeless;
|
||||||
|
|
||||||
|
edgeless.mountElm.append(groupEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mountConnectorLabelEditor(
|
export function mountConnectorLabelEditor(
|
||||||
@@ -112,6 +115,12 @@ export function mountConnectorLabelEditor(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
edgeless.gfx.tool.setTool('default');
|
||||||
|
edgeless.gfx.selection.set({
|
||||||
|
elements: [connector.id],
|
||||||
|
editing: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (!connector.text) {
|
if (!connector.text) {
|
||||||
const text = new Y.Text();
|
const text = new Y.Text();
|
||||||
const labelOffset = connector.labelOffset;
|
const labelOffset = connector.labelOffset;
|
||||||
@@ -143,9 +152,4 @@ export function mountConnectorLabelEditor(
|
|||||||
editor.inlineEditor?.focusEnd();
|
editor.inlineEditor?.focusEnd();
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
edgeless.gfx.tool.setTool('default');
|
|
||||||
edgeless.gfx.selection.set({
|
|
||||||
elements: [connector.id],
|
|
||||||
editing: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export class EditorToolbar extends WithDisposable(LitElement) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
this._disposables.addFromEvent(this, 'wheel', stopPropagation);
|
this._disposables.addFromEvent(this, 'wheel', stopPropagation, {
|
||||||
|
passive: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { ToolbarContext } from './context';
|
|||||||
|
|
||||||
export enum ActionPlacement {
|
export enum ActionPlacement {
|
||||||
Start = 0,
|
Start = 0,
|
||||||
|
Normal = 1 << 0,
|
||||||
End = 1 << 1,
|
End = 1 << 1,
|
||||||
More = 1 << 2,
|
More = 1 << 2,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
type BlockComponent,
|
type BlockComponent,
|
||||||
BlockSelection,
|
BlockSelection,
|
||||||
type BlockStdScope,
|
type BlockStdScope,
|
||||||
|
SurfaceSelection,
|
||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||||
import { nextTick } from '@blocksuite/global/utils';
|
import { nextTick } from '@blocksuite/global/utils';
|
||||||
@@ -61,7 +62,8 @@ abstract class ToolbarContextBase {
|
|||||||
if (this.flags.accept()) return true;
|
if (this.flags.accept()) return true;
|
||||||
if (this.host.event.active) return true;
|
if (this.host.event.active) return true;
|
||||||
// Selects `embed-synced-doc-block`
|
// Selects `embed-synced-doc-block`
|
||||||
return this.host.contains(document.activeElement);
|
if (this.host.contains(document.activeElement)) return true;
|
||||||
|
return this.isEdgelessMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
get readonly() {
|
get readonly() {
|
||||||
@@ -108,6 +110,26 @@ abstract class ToolbarContextBase {
|
|||||||
return this.toolbarRegistry.message$;
|
return this.toolbarRegistry.message$;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentElement() {
|
||||||
|
const selection = this.selection.find(SurfaceSelection);
|
||||||
|
return selection?.elements.length
|
||||||
|
? this.gfx.getElementById(selection.elements[0])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentBlock(): Block | null {
|
||||||
|
return this.getCurrentBlockBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentBlockComponent(): BlockComponent | null {
|
||||||
|
const block = this.getCurrentBlock();
|
||||||
|
return block && this.view.getBlock(block.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentModel() {
|
||||||
|
return this.getCurrentBlock()?.model ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentBlockBy<T extends SelectionConstructor>(type?: T): Block | null {
|
getCurrentBlockBy<T extends SelectionConstructor>(type?: T): Block | null {
|
||||||
const selection = this.selection.find(type ?? BlockSelection);
|
const selection = this.selection.find(type ?? BlockSelection);
|
||||||
return (selection && this.store.getBlock(selection.blockId)) ?? null;
|
return (selection && this.store.getBlock(selection.blockId)) ?? null;
|
||||||
@@ -125,11 +147,10 @@ abstract class ToolbarContextBase {
|
|||||||
return matchModels(model, [klass]) ? model : null;
|
return matchModels(model, [klass]) ? model : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentBlockComponentBy<
|
getCurrentBlockComponentBy<K extends abstract new (...args: any) => any>(
|
||||||
T extends SelectionConstructor,
|
klass: K
|
||||||
K extends abstract new (...args: any) => any,
|
): InstanceType<K> | null {
|
||||||
>(type: T, klass: K): InstanceType<K> | null {
|
const block = this.getCurrentBlockBy();
|
||||||
const block = this.getCurrentBlockBy<T>(type);
|
|
||||||
const component = block && this.view.getBlock(block.id);
|
const component = block && this.view.getBlock(block.id);
|
||||||
return this.blockComponentIs(component, klass) ? component : null;
|
return this.blockComponentIs(component, klass) ? component : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ export class Flags {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSurface() {
|
||||||
|
return this.check(Flag.Surface);
|
||||||
|
}
|
||||||
|
|
||||||
isText() {
|
isText() {
|
||||||
return this.check(Flag.Text);
|
return this.check(Flag.Text);
|
||||||
}
|
}
|
||||||
@@ -74,6 +78,10 @@ export class Flags {
|
|||||||
return this.check(Flag.Native);
|
return this.check(Flag.Native);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isHovering() {
|
||||||
|
return this.check(Flag.Hovering);
|
||||||
|
}
|
||||||
|
|
||||||
accept() {
|
accept() {
|
||||||
return this.check(Flag.Accepting);
|
return this.check(Flag.Accepting);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ import {
|
|||||||
ListBlockModel,
|
ListBlockModel,
|
||||||
ParagraphBlockModel,
|
ParagraphBlockModel,
|
||||||
} from '@blocksuite/affine-model';
|
} from '@blocksuite/affine-model';
|
||||||
import {
|
|
||||||
getBlockSelectionsCommand,
|
|
||||||
getSelectedBlocksCommand,
|
|
||||||
} from '@blocksuite/affine-shared/commands';
|
|
||||||
import {
|
import {
|
||||||
ToolbarContext,
|
ToolbarContext,
|
||||||
ToolbarFlag as Flag,
|
ToolbarFlag as Flag,
|
||||||
@@ -18,20 +14,31 @@ import {
|
|||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||||
import {
|
import {
|
||||||
|
type BlockComponent,
|
||||||
BlockSelection,
|
BlockSelection,
|
||||||
SurfaceSelection,
|
|
||||||
TextSelection,
|
TextSelection,
|
||||||
WidgetComponent,
|
WidgetComponent,
|
||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
import {
|
||||||
import { Bound, getCommonBound } from '@blocksuite/global/gfx';
|
GfxBlockElementModel,
|
||||||
|
type GfxController,
|
||||||
|
type GfxModel,
|
||||||
|
GfxPrimitiveElementModel,
|
||||||
|
} from '@blocksuite/block-std/gfx';
|
||||||
|
import {
|
||||||
|
Bound,
|
||||||
|
getCommonBound,
|
||||||
|
getCommonBoundWithRotation,
|
||||||
|
} from '@blocksuite/global/gfx';
|
||||||
import { nextTick } from '@blocksuite/global/utils';
|
import { nextTick } from '@blocksuite/global/utils';
|
||||||
import type { Placement, ReferenceElement } from '@floating-ui/dom';
|
import type { Placement, ReferenceElement, SideObject } from '@floating-ui/dom';
|
||||||
import { batch, effect, signal } from '@preact/signals-core';
|
import { batch, effect, signal } from '@preact/signals-core';
|
||||||
import { css } from 'lit';
|
import { css } from 'lit';
|
||||||
|
import groupBy from 'lodash-es/groupBy';
|
||||||
import throttle from 'lodash-es/throttle';
|
import throttle from 'lodash-es/throttle';
|
||||||
|
import toPairs from 'lodash-es/toPairs';
|
||||||
|
|
||||||
import { autoUpdatePosition, renderToolbar } from './utils';
|
import { autoUpdatePosition, renderToolbar, sideMap } from './utils';
|
||||||
|
|
||||||
export const AFFINE_TOOLBAR_WIDGET = 'affine-toolbar-widget';
|
export const AFFINE_TOOLBAR_WIDGET = 'affine-toolbar-widget';
|
||||||
|
|
||||||
@@ -47,7 +54,7 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
z-index: var(--affine-z-index-popover);
|
z-index: var(--affine-z-index-popover);
|
||||||
|
|
||||||
will-change: opacity, transform;
|
will-change: opacity, overlay, display, transform;
|
||||||
transition-property: opacity, overlay, display;
|
transition-property: opacity, overlay, display;
|
||||||
transition-duration: 120ms;
|
transition-duration: 120ms;
|
||||||
transition-timing-function: ease-out;
|
transition-timing-function: ease-out;
|
||||||
@@ -65,10 +72,63 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
range$ = signal<Range | null>(null);
|
|
||||||
|
|
||||||
flavour$ = signal('affine:note');
|
flavour$ = signal('affine:note');
|
||||||
|
|
||||||
|
placement$ = signal<Placement>('top');
|
||||||
|
|
||||||
|
sideOptions$ = signal<Partial<SideObject> | null>(null);
|
||||||
|
|
||||||
|
referenceElement$ = signal<(() => ReferenceElement | null) | null>(null);
|
||||||
|
|
||||||
|
setReferenceElementWithRange(range: Range | null) {
|
||||||
|
this.referenceElement$.value = range
|
||||||
|
? () => ({
|
||||||
|
getBoundingClientRect: () => range.getBoundingClientRect(),
|
||||||
|
getClientRects: () =>
|
||||||
|
Array.from(range.getClientRects()).filter(rect =>
|
||||||
|
Math.round(rect.width)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReferenceElementWithHtmlElement(element: Element | null) {
|
||||||
|
this.referenceElement$.value = element ? () => element : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReferenceElementWithBlocks(blocks: BlockComponent[]) {
|
||||||
|
const getClientRects = () => blocks.map(e => e.getBoundingClientRect());
|
||||||
|
|
||||||
|
this.referenceElement$.value = blocks.length
|
||||||
|
? () => ({
|
||||||
|
getBoundingClientRect: () => {
|
||||||
|
const rects = getClientRects();
|
||||||
|
const bounds = getCommonBound(rects.map(Bound.fromDOMRect));
|
||||||
|
if (!bounds) return rects[0];
|
||||||
|
return new DOMRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
||||||
|
},
|
||||||
|
getClientRects,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReferenceElementWithElements(gfx: GfxController, elements: GfxModel[]) {
|
||||||
|
const getBoundingClientRect = () => {
|
||||||
|
const bounds = getCommonBoundWithRotation(elements);
|
||||||
|
const { x: offsetX, y: offsetY } = this.getBoundingClientRect();
|
||||||
|
const [x, y, w, h] = gfx.viewport.toViewBound(bounds).toXYWH();
|
||||||
|
const rect = new DOMRect(x + offsetX, y + offsetY, w, h);
|
||||||
|
return rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.referenceElement$.value = elements.length
|
||||||
|
? () => ({
|
||||||
|
getBoundingClientRect,
|
||||||
|
getClientRects: () => [getBoundingClientRect()],
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
toolbar = new EditorToolbar();
|
toolbar = new EditorToolbar();
|
||||||
|
|
||||||
get toolbarRegistry() {
|
get toolbarRegistry() {
|
||||||
@@ -80,7 +140,9 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
flavour$,
|
flavour$,
|
||||||
range$,
|
placement$,
|
||||||
|
sideOptions$,
|
||||||
|
referenceElement$,
|
||||||
disposables,
|
disposables,
|
||||||
toolbar,
|
toolbar,
|
||||||
toolbarRegistry,
|
toolbarRegistry,
|
||||||
@@ -98,22 +160,25 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
// Selects text in note.
|
// Selects text in note.
|
||||||
disposables.add(
|
disposables.add(
|
||||||
std.selection.find$(TextSelection).subscribe(result => {
|
std.selection.find$(TextSelection).subscribe(result => {
|
||||||
const activated =
|
const range = std.range.value ?? null;
|
||||||
|
const activated = Boolean(
|
||||||
context.activated &&
|
context.activated &&
|
||||||
Boolean(
|
range &&
|
||||||
result &&
|
result &&
|
||||||
!result.isCollapsed() &&
|
!result.isCollapsed() &&
|
||||||
result.from.length + (result.to?.length ?? 0)
|
result.from.length + (result.to?.length ?? 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
flags.toggle(Flag.Text, activated);
|
flags.toggle(Flag.Text, activated);
|
||||||
|
|
||||||
if (!activated) return;
|
if (!activated) return;
|
||||||
|
|
||||||
const range = std.range.value ?? null;
|
this.setReferenceElementWithRange(range);
|
||||||
range$.value = activated ? range : null;
|
|
||||||
|
|
||||||
|
sideOptions$.value = null;
|
||||||
|
flavour$.value = 'affine:note';
|
||||||
|
placement$.value = 'top';
|
||||||
flags.refresh(Flag.Text);
|
flags.refresh(Flag.Text);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -124,54 +189,68 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
disposables.addFromEvent(document, 'selectionchange', () => {
|
disposables.addFromEvent(document, 'selectionchange', () => {
|
||||||
const range = std.range.value ?? null;
|
const range = std.range.value ?? null;
|
||||||
let activated = context.activated && Boolean(range && !range.collapsed);
|
let activated = context.activated && Boolean(range && !range.collapsed);
|
||||||
|
let isNative = false;
|
||||||
|
|
||||||
if (activated) {
|
if (activated) {
|
||||||
const result = std.selection.find(DatabaseSelection);
|
const result = std.selection.find(DatabaseSelection);
|
||||||
const viewSelection = result?.viewSelection;
|
const viewSelection = result?.viewSelection;
|
||||||
|
if (viewSelection) {
|
||||||
activated = Boolean(
|
isNative =
|
||||||
viewSelection &&
|
(viewSelection.selectionType === 'area' &&
|
||||||
((viewSelection.selectionType === 'area' &&
|
|
||||||
viewSelection.isEditing) ||
|
viewSelection.isEditing) ||
|
||||||
(viewSelection.selectionType === 'cell' &&
|
(viewSelection.selectionType === 'cell' && viewSelection.isEditing);
|
||||||
viewSelection.isEditing))
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (!activated) {
|
if (!isNative) {
|
||||||
const result = std.selection.find(TableSelection);
|
const result = std.selection.find(TableSelection);
|
||||||
const viewSelection = result?.data;
|
const viewSelection = result?.data;
|
||||||
activated = Boolean(viewSelection && viewSelection.type === 'area');
|
if (viewSelection) {
|
||||||
|
isNative = viewSelection.type === 'area';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
|
activated &&= isNative;
|
||||||
|
|
||||||
|
// Focues outside: `doc-title`
|
||||||
|
if (
|
||||||
|
flags.check(Flag.Text) &&
|
||||||
|
!std.host.contains(range?.commonAncestorContainer ?? null)
|
||||||
|
) {
|
||||||
|
flags.toggle(Flag.Text, false);
|
||||||
|
}
|
||||||
|
|
||||||
flags.toggle(Flag.Native, activated);
|
flags.toggle(Flag.Native, activated);
|
||||||
|
|
||||||
if (!activated) return;
|
if (!activated) return;
|
||||||
|
|
||||||
range$.value = activated ? range : null;
|
this.setReferenceElementWithRange(range);
|
||||||
flavour$.value = 'affine:note';
|
|
||||||
|
|
||||||
|
sideOptions$.value = null;
|
||||||
|
flavour$.value = 'affine:note';
|
||||||
|
placement$.value = 'top';
|
||||||
flags.refresh(Flag.Native);
|
flags.refresh(Flag.Native);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Selects blocks in note.
|
// Selects blocks in note.
|
||||||
disposables.add(
|
disposables.add(
|
||||||
std.selection.filter$(BlockSelection).subscribe(result => {
|
std.selection.filter$(BlockSelection).subscribe(selections => {
|
||||||
const count = result.length;
|
const blockIds = selections.map(s => s.blockId);
|
||||||
|
const count = blockIds.length;
|
||||||
let flavour = 'affine:note';
|
let flavour = 'affine:note';
|
||||||
let activated = context.activated && Boolean(count);
|
let activated = context.activated && Boolean(count);
|
||||||
|
|
||||||
if (activated) {
|
if (activated) {
|
||||||
// Handles a signal block.
|
// Handles a signal block.
|
||||||
const block = count === 1 && std.store.getBlock(result[0].blockId);
|
const block = count === 1 && std.store.getBlock(blockIds[0]);
|
||||||
|
|
||||||
// Chencks if block's config exists.
|
// Chencks if block's config exists.
|
||||||
if (block) {
|
if (block) {
|
||||||
const modelFlavour = block.model.flavour;
|
const modelFlavour = block.model.flavour;
|
||||||
const existed =
|
const existed =
|
||||||
toolbarRegistry.modules.has(modelFlavour) ||
|
toolbarRegistry.modules.has(modelFlavour) ??
|
||||||
toolbarRegistry.modules.has(`custom:${modelFlavour}`);
|
toolbarRegistry.modules.has(`custom:${modelFlavour}`);
|
||||||
if (existed) {
|
if (existed) {
|
||||||
flavour = modelFlavour;
|
flavour = modelFlavour;
|
||||||
@@ -187,12 +266,19 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
flavour$.value = flavour;
|
|
||||||
|
|
||||||
flags.toggle(Flag.Block, activated);
|
flags.toggle(Flag.Block, activated);
|
||||||
|
|
||||||
if (!activated) return;
|
if (!activated) return;
|
||||||
|
|
||||||
|
this.setReferenceElementWithBlocks(
|
||||||
|
blockIds
|
||||||
|
.map(id => std.view.getBlock(id))
|
||||||
|
.filter(block => block !== null)
|
||||||
|
);
|
||||||
|
|
||||||
|
sideOptions$.value = null;
|
||||||
|
flavour$.value = flavour;
|
||||||
|
placement$.value = flavour === 'affine:note' ? 'top' : 'top-start';
|
||||||
flags.refresh(Flag.Block);
|
flags.refresh(Flag.Block);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -201,12 +287,78 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
// Selects elements in edgeless.
|
// Selects elements in edgeless.
|
||||||
// Triggered only when not in editing state.
|
// Triggered only when not in editing state.
|
||||||
disposables.add(
|
disposables.add(
|
||||||
std.selection.filter$(SurfaceSelection).subscribe(result => {
|
context.gfx.selection.slots.updated.subscribe(selections => {
|
||||||
|
// TODO(@fundon): should remove it when edgeless element toolbar is removed
|
||||||
|
if (context.isEdgelessMode) return;
|
||||||
|
|
||||||
|
const elementIds = selections
|
||||||
|
.map(s => (s.editing || s.inoperable ? [] : s.elements))
|
||||||
|
.flat();
|
||||||
|
const count = elementIds.length;
|
||||||
|
const gfx = context.gfx;
|
||||||
|
const surface = gfx.surface;
|
||||||
const activated =
|
const activated =
|
||||||
context.activated &&
|
context.activated && Boolean(surface) && Boolean(count);
|
||||||
Boolean(result.length) &&
|
let flavour = 'affine:surface';
|
||||||
!result.some(e => e.editing);
|
let elements: GfxModel[] = [];
|
||||||
flags.toggle(Flag.Surface, activated);
|
let hasLocked = false;
|
||||||
|
let sideOptions = null;
|
||||||
|
|
||||||
|
if (activated && surface) {
|
||||||
|
elements = elementIds
|
||||||
|
.map(id => gfx.getElementById(id))
|
||||||
|
.filter(model => model !== null) as GfxModel[];
|
||||||
|
|
||||||
|
hasLocked = elements.some(e => e.isLocked());
|
||||||
|
|
||||||
|
const grouped = groupBy(
|
||||||
|
elements.map(model => {
|
||||||
|
let flavour = surface.flavour;
|
||||||
|
|
||||||
|
if (model instanceof GfxBlockElementModel) {
|
||||||
|
flavour += `:${model.flavour.split(':').pop()}`;
|
||||||
|
} else if (model instanceof GfxPrimitiveElementModel) {
|
||||||
|
flavour += `:${model.type}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { model, flavour };
|
||||||
|
}),
|
||||||
|
e => e.flavour
|
||||||
|
);
|
||||||
|
|
||||||
|
const paired = toPairs(grouped);
|
||||||
|
|
||||||
|
if (paired.length === 1) {
|
||||||
|
flavour = paired[0][0];
|
||||||
|
if (
|
||||||
|
flavour === 'affine:surface:shape' &&
|
||||||
|
paired[0][1].length === 1
|
||||||
|
) {
|
||||||
|
sideOptions = sideMap.get(flavour) ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sideOptions) {
|
||||||
|
const flavours = new Set(paired.map(([f]) => f));
|
||||||
|
if (flavours.has('affine:surface:frame')) {
|
||||||
|
sideOptions = sideMap.get('affine:surface:frame') ?? null;
|
||||||
|
} else if (flavours.has('affine:surface:group')) {
|
||||||
|
sideOptions = sideMap.get('affine:surface:group') ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batch(() => {
|
||||||
|
flags.toggle(Flag.Surface, activated);
|
||||||
|
|
||||||
|
if (!activated || !flavour) return;
|
||||||
|
|
||||||
|
this.setReferenceElementWithElements(gfx, elements);
|
||||||
|
|
||||||
|
sideOptions$.value = sideOptions;
|
||||||
|
flavour$.value = flavour;
|
||||||
|
placement$.value = hasLocked ? 'top' : 'top-start';
|
||||||
|
flags.refresh(Flag.Surface);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -223,7 +375,8 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
if (!hasTextSelection) return;
|
if (!hasTextSelection) return;
|
||||||
|
|
||||||
const range = std.range.value ?? null;
|
const range = std.range.value ?? null;
|
||||||
range$.value = range && !range.collapsed ? range : null;
|
|
||||||
|
this.setReferenceElementWithRange(range);
|
||||||
|
|
||||||
// TODO(@fundon): maybe here can be further optimized
|
// TODO(@fundon): maybe here can be further optimized
|
||||||
// 1. Prevents flickering effects.
|
// 1. Prevents flickering effects.
|
||||||
@@ -236,21 +389,27 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// TODO(@fundon): improve these cases
|
// TODO(@fundon): improve these cases
|
||||||
// When switch the view mode, wait until the view is created
|
// Waits until the view is created when switching the view mode.
|
||||||
// `card view` or `embed view`
|
// `card view` or `embed view`
|
||||||
disposables.add(
|
disposables.add(
|
||||||
std.view.viewUpdated.subscribe(record => {
|
std.view.viewUpdated.subscribe(record => {
|
||||||
if (
|
if (record.type !== 'block') return;
|
||||||
record.type === 'block' &&
|
if (!flags.isBlock()) return;
|
||||||
flags.isBlock() &&
|
|
||||||
std.selection
|
const blockIds = std.selection
|
||||||
.filter$(BlockSelection)
|
.filter$(BlockSelection)
|
||||||
.peek()
|
.peek()
|
||||||
.find(s => s.blockId === record.id)
|
.map(s => s.blockId);
|
||||||
) {
|
|
||||||
if (record.method === 'add') {
|
if (record.method === 'add' && blockIds.includes(record.id)) {
|
||||||
|
batch(() => {
|
||||||
|
this.setReferenceElementWithBlocks(
|
||||||
|
blockIds
|
||||||
|
.map(id => std.view.getBlock(id))
|
||||||
|
.filter(block => block !== null)
|
||||||
|
);
|
||||||
flags.refresh(Flag.Block);
|
flags.refresh(Flag.Block);
|
||||||
}
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -302,9 +461,9 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
eventOptions
|
eventOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handles hover elements
|
// Handles element when hovering
|
||||||
disposables.add(
|
disposables.add(
|
||||||
toolbarRegistry.message$.subscribe(data => {
|
message$.subscribe(data => {
|
||||||
if (
|
if (
|
||||||
!context.activated ||
|
!context.activated ||
|
||||||
flags.contains(Flag.Text | Flag.Native | Flag.Block)
|
flags.contains(Flag.Text | Flag.Native | Flag.Block)
|
||||||
@@ -324,130 +483,85 @@ export class AffineToolbarWidget extends WidgetComponent {
|
|||||||
|
|
||||||
setFloating(toolbar);
|
setFloating(toolbar);
|
||||||
|
|
||||||
flavour$.value = flavour;
|
this.setReferenceElementWithHtmlElement(data.element);
|
||||||
|
|
||||||
|
sideOptions$.value = null;
|
||||||
|
flavour$.value = flavour;
|
||||||
|
placement$.value = 'top';
|
||||||
flags.refresh(Flag.Hovering);
|
flags.refresh(Flag.Hovering);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should update position of notes' toolbar in edgeless
|
|
||||||
disposables.add(
|
|
||||||
this.std
|
|
||||||
.get(GfxControllerIdentifier)
|
|
||||||
.viewport.viewportUpdated.subscribe(() => {
|
|
||||||
if (!context.activated) return;
|
|
||||||
|
|
||||||
if (flags.value === Flag.None || flags.check(Flag.Hiding)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags.isText()) {
|
|
||||||
flags.refresh(Flag.Text);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags.isNative()) {
|
|
||||||
flags.refresh(Flag.Native);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags.isBlock()) {
|
|
||||||
flags.refresh(Flag.Block);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
disposables.add(
|
|
||||||
flags.value$.subscribe(value => {
|
|
||||||
// Hides toolbar
|
|
||||||
if (value === Flag.None || flags.check(Flag.Hiding, value)) {
|
|
||||||
delete toolbar.dataset.open;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shows toolbar
|
|
||||||
// 1. `Flag.Text`: formatting in note
|
|
||||||
// 2. `Flag.Native`: formating in database
|
|
||||||
// 3. `Flag.Block`: blocks in note
|
|
||||||
// 4. `Flag.Hovering`: inline links in note/database
|
|
||||||
if (
|
|
||||||
flags.contains(
|
|
||||||
Flag.Hovering | Flag.Text | Flag.Native | Flag.Block,
|
|
||||||
value
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
renderToolbar(toolbar, context, flavour$.peek());
|
|
||||||
toolbar.dataset.open = 'true';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shows toolbar in edgeles
|
|
||||||
// TODO(@fundon): handles edgeless toolbar
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
disposables.add(
|
disposables.add(
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const value = flags.value$.value;
|
const value = flags.value$.value;
|
||||||
|
|
||||||
|
// Hides toolbar
|
||||||
|
if (value === Flag.None || flags.check(Flag.Hiding, value)) {
|
||||||
|
if (toolbar.dataset.open) delete toolbar.dataset.open;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const flavour = flavour$.value;
|
const flavour = flavour$.value;
|
||||||
if (!context.activated || flags.contains(Flag.Hiding, value)) return;
|
|
||||||
|
// Shows toolbar
|
||||||
|
// 1. `Flag.Text`: formatting in note
|
||||||
|
// 2. `Flag.Native`: formating in database/table
|
||||||
|
// 3. `Flag.Block`: blocks in note
|
||||||
|
// 4. `Flag.Hovering`: inline links in note/database/table
|
||||||
|
// 5. `Flag.Surface`: elements in edgeless
|
||||||
|
renderToolbar(toolbar, context, flavour);
|
||||||
|
if (toolbar.dataset.open) return;
|
||||||
|
toolbar.dataset.open = 'true';
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let abortController = new AbortController();
|
||||||
|
|
||||||
|
disposables.add(
|
||||||
|
effect(() => {
|
||||||
|
if (!abortController.signal.aborted) {
|
||||||
|
abortController.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = flags.value$.value;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!flags.contains(
|
!context.activated ||
|
||||||
Flag.Hovering | Flag.Text | Flag.Native | Flag.Block,
|
Flag.None === value ||
|
||||||
value
|
flags.contains(Flag.Hiding, value)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO(@fundon): improves here
|
const build = referenceElement$.value;
|
||||||
const isNote = flavour === 'affine:note';
|
const referenceElement = build?.();
|
||||||
let placement = isNote ? ('top' as Placement) : undefined;
|
if (!referenceElement) return;
|
||||||
let virtualEl: ReferenceElement | null = null;
|
|
||||||
|
|
||||||
if (flags.check(Flag.Hovering, value)) {
|
const flavour = flavour$.value;
|
||||||
const message = message$.value;
|
const placement = placement$.value;
|
||||||
if (!message) return;
|
const sideOptions = sideOptions$.value;
|
||||||
|
|
||||||
const { element } = message;
|
if (abortController.signal.aborted) {
|
||||||
|
abortController = new AbortController();
|
||||||
virtualEl = element;
|
|
||||||
placement = 'top';
|
|
||||||
} else if (flags.check(Flag.Block, value)) {
|
|
||||||
const [ok, { selectedBlocks }] = context.chain
|
|
||||||
.pipe(getBlockSelectionsCommand)
|
|
||||||
.pipe(getSelectedBlocksCommand, { types: ['block'] })
|
|
||||||
.run();
|
|
||||||
|
|
||||||
if (!ok || !selectedBlocks?.length) return;
|
|
||||||
|
|
||||||
virtualEl = {
|
|
||||||
getBoundingClientRect: () => {
|
|
||||||
const rects = selectedBlocks.map(e => e.getBoundingClientRect());
|
|
||||||
const bounds = getCommonBound(rects.map(Bound.fromDOMRect));
|
|
||||||
if (!bounds) return rects[0];
|
|
||||||
return new DOMRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
|
||||||
},
|
|
||||||
getClientRects: () =>
|
|
||||||
selectedBlocks.map(e => e.getBoundingClientRect()),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const range = range$.value;
|
|
||||||
if (!range) return;
|
|
||||||
|
|
||||||
virtualEl = {
|
|
||||||
getBoundingClientRect: () => range.getBoundingClientRect(),
|
|
||||||
getClientRects: () =>
|
|
||||||
Array.from(range.getClientRects()).filter(rect =>
|
|
||||||
Math.round(rect.width)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
const signal = abortController.signal;
|
||||||
|
|
||||||
if (!virtualEl) return;
|
const cleanup = autoUpdatePosition(
|
||||||
|
signal,
|
||||||
|
toolbar,
|
||||||
|
referenceElement,
|
||||||
|
flavour,
|
||||||
|
placement,
|
||||||
|
sideOptions
|
||||||
|
);
|
||||||
|
|
||||||
return autoUpdatePosition(virtualEl, toolbar, placement);
|
signal.addEventListener('abort', cleanup, { once: true });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (signal.aborted) return;
|
||||||
|
abortController.abort();
|
||||||
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ import {
|
|||||||
type ToolbarAction,
|
type ToolbarAction,
|
||||||
type ToolbarActions,
|
type ToolbarActions,
|
||||||
type ToolbarContext,
|
type ToolbarContext,
|
||||||
type ToolbarModuleConfig,
|
|
||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
import { BlockSelection } from '@blocksuite/block-std';
|
|
||||||
import { nextTick } from '@blocksuite/global/utils';
|
import { nextTick } from '@blocksuite/global/utils';
|
||||||
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
|
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
|
||||||
import type {
|
import type {
|
||||||
AutoUpdateOptions,
|
AutoUpdateOptions,
|
||||||
Placement,
|
Placement,
|
||||||
ReferenceElement,
|
ReferenceElement,
|
||||||
|
SideObject,
|
||||||
} from '@floating-ui/dom';
|
} from '@floating-ui/dom';
|
||||||
import {
|
import {
|
||||||
autoUpdate,
|
autoUpdate,
|
||||||
@@ -38,54 +37,83 @@ import orderBy from 'lodash-es/orderBy';
|
|||||||
import partition from 'lodash-es/partition';
|
import partition from 'lodash-es/partition';
|
||||||
import toPairs from 'lodash-es/toPairs';
|
import toPairs from 'lodash-es/toPairs';
|
||||||
|
|
||||||
|
export const sideMap = new Map([
|
||||||
|
// includes frame element
|
||||||
|
['affine:surface:frame', { top: 28 }],
|
||||||
|
// includes group element
|
||||||
|
['affine:surface:group', { top: 20 }],
|
||||||
|
// only one shape element
|
||||||
|
['affine:surface:shape', { top: 26, bottom: -26 }],
|
||||||
|
]);
|
||||||
|
|
||||||
export function autoUpdatePosition(
|
export function autoUpdatePosition(
|
||||||
referenceElement: ReferenceElement,
|
signal: AbortSignal,
|
||||||
toolbar: EditorToolbar,
|
toolbar: EditorToolbar,
|
||||||
placement: Placement = 'top-start',
|
referenceElement: ReferenceElement,
|
||||||
|
flavour: string,
|
||||||
|
placement: Placement,
|
||||||
|
sideOptions: Partial<SideObject> | null,
|
||||||
options: AutoUpdateOptions = { elementResize: false, animationFrame: true }
|
options: AutoUpdateOptions = { elementResize: false, animationFrame: true }
|
||||||
) {
|
) {
|
||||||
const abortController = new AbortController();
|
const isInline = flavour === 'affine:note';
|
||||||
const signal = abortController.signal;
|
const hasSurfaceScope = flavour.includes('surface');
|
||||||
|
const offsetTop = sideOptions?.top ?? 0;
|
||||||
|
const offsetBottom = sideOptions?.bottom ?? 0;
|
||||||
|
const offsetY = offsetTop + (hasSurfaceScope ? 2 : 0);
|
||||||
|
const config = {
|
||||||
|
placement,
|
||||||
|
middleware: [
|
||||||
|
offset(10 + offsetY),
|
||||||
|
isInline ? inline() : undefined,
|
||||||
|
shift(state => ({
|
||||||
|
padding: {
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
bottom: 150,
|
||||||
|
left: 10,
|
||||||
|
},
|
||||||
|
crossAxis: state.placement.includes('bottom'),
|
||||||
|
limiter: limitShift(),
|
||||||
|
})),
|
||||||
|
flip({ padding: 10 }),
|
||||||
|
hide(),
|
||||||
|
],
|
||||||
|
};
|
||||||
const update = async () => {
|
const update = async () => {
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
const listener = () => resolve(signal.reason);
|
signal.addEventListener('abort', () => resolve(signal.reason), {
|
||||||
signal.addEventListener('abort', listener, { once: true });
|
once: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) return;
|
||||||
|
|
||||||
signal.removeEventListener('abort', listener);
|
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}),
|
}),
|
||||||
toolbar.updateComplete.then(nextTick),
|
isInline ? toolbar.updateComplete.then(nextTick) : toolbar.updateComplete,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) return;
|
||||||
|
|
||||||
const { x, y } = await computePosition(referenceElement, toolbar, {
|
const result = await computePosition(referenceElement, toolbar, config);
|
||||||
placement,
|
|
||||||
middleware: [
|
const { x, middlewareData, placement: currentPlacement } = result;
|
||||||
offset(10),
|
const y =
|
||||||
inline(),
|
result.y -
|
||||||
shift(state => ({
|
(currentPlacement.includes('top') ? 0 : offsetTop + offsetBottom);
|
||||||
padding: {
|
|
||||||
top: 10,
|
|
||||||
right: 10,
|
|
||||||
bottom: 150,
|
|
||||||
left: 10,
|
|
||||||
},
|
|
||||||
crossAxis: state.placement.includes('bottom'),
|
|
||||||
limiter: limitShift(),
|
|
||||||
})),
|
|
||||||
flip({ padding: 10 }),
|
|
||||||
hide(),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
toolbar.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
toolbar.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
||||||
|
|
||||||
|
if (toolbar.dataset.open) {
|
||||||
|
if (middlewareData.hide?.referenceHidden) {
|
||||||
|
delete toolbar.dataset.open;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toolbar.dataset.open = 'true';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanup = autoUpdate(
|
return autoUpdate(
|
||||||
referenceElement,
|
referenceElement,
|
||||||
toolbar,
|
toolbar,
|
||||||
() => {
|
() => {
|
||||||
@@ -93,15 +121,37 @@ export function autoUpdatePosition(
|
|||||||
},
|
},
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
|
||||||
cleanup();
|
|
||||||
if (signal.aborted) return;
|
|
||||||
abortController.abort();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function group(actions: ToolbarAction[]) {
|
export function combine(actions: ToolbarActions, context: ToolbarContext) {
|
||||||
|
const grouped = group(actions);
|
||||||
|
|
||||||
|
const generated = grouped.map(action => {
|
||||||
|
const newAction = {
|
||||||
|
...action,
|
||||||
|
placement: action.placement ?? ActionPlacement.Normal,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('generate' in action && typeof action.generate === 'function') {
|
||||||
|
// TODO(@fundon): should delete `generate` fn
|
||||||
|
return {
|
||||||
|
...newAction,
|
||||||
|
...action.generate(context),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return newAction;
|
||||||
|
});
|
||||||
|
|
||||||
|
const filtered = generated.filter(action => {
|
||||||
|
if (typeof action.when === 'function') return action.when(context);
|
||||||
|
return action.when ?? true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
function group(actions: ToolbarAction[]): ToolbarAction[] {
|
||||||
const grouped = groupBy(actions, a => a.id);
|
const grouped = groupBy(actions, a => a.id);
|
||||||
|
|
||||||
const paired = toPairs(grouped).map(([_, items]) => {
|
const paired = toPairs(grouped).map(([_, items]) => {
|
||||||
@@ -114,28 +164,6 @@ function group(actions: ToolbarAction[]) {
|
|||||||
return paired;
|
return paired;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function combine(actions: ToolbarActions, context: ToolbarContext) {
|
|
||||||
const grouped = group(actions);
|
|
||||||
|
|
||||||
const generated = grouped.map(action => {
|
|
||||||
if ('generate' in action && action.generate) {
|
|
||||||
// TODO(@fundon): should delete `generate` fn
|
|
||||||
return {
|
|
||||||
...action,
|
|
||||||
...action.generate(context),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return action;
|
|
||||||
});
|
|
||||||
|
|
||||||
const filtered = generated.filter(action => {
|
|
||||||
if (typeof action.when === 'function') return action.when(context);
|
|
||||||
return action.when ?? true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
const merge = (a: any, b: any) =>
|
const merge = (a: any, b: any) =>
|
||||||
mergeWith(a, b, (obj, src) =>
|
mergeWith(a, b, (obj, src) =>
|
||||||
Array.isArray(obj) ? group(obj.concat(src)) : src
|
Array.isArray(obj) ? group(obj.concat(src)) : src
|
||||||
@@ -155,27 +183,23 @@ export function renderToolbar(
|
|||||||
context: ToolbarContext,
|
context: ToolbarContext,
|
||||||
flavour: string
|
flavour: string
|
||||||
) {
|
) {
|
||||||
|
const hasSurfaceScope = flavour.includes('surface');
|
||||||
const toolbarRegistry = context.toolbarRegistry;
|
const toolbarRegistry = context.toolbarRegistry;
|
||||||
const module = toolbarRegistry.modules.get(flavour);
|
|
||||||
if (!module) return;
|
|
||||||
const customModule = toolbarRegistry.modules.get(`custom:${flavour}`);
|
|
||||||
const customWildcardModule = toolbarRegistry.modules.get(`custom:affine:*`);
|
|
||||||
const config = module.config satisfies ToolbarModuleConfig;
|
|
||||||
const customConfig = (customModule?.config ?? {
|
|
||||||
actions: [],
|
|
||||||
}) satisfies ToolbarModuleConfig;
|
|
||||||
const customWildcardConfig = (customWildcardModule?.config ?? {
|
|
||||||
actions: [],
|
|
||||||
}) satisfies ToolbarModuleConfig;
|
|
||||||
|
|
||||||
const combined = combine(
|
const actions = [
|
||||||
[
|
flavour,
|
||||||
...config.actions,
|
`custom:${flavour}`,
|
||||||
...customConfig.actions,
|
hasSurfaceScope ? ['affine:surface:*', 'custom:affine:surface:*'] : [],
|
||||||
...customWildcardConfig.actions,
|
'affine:*',
|
||||||
],
|
'custom:affine:*',
|
||||||
context
|
]
|
||||||
);
|
.flat()
|
||||||
|
.map(key => toolbarRegistry.modules.get(key))
|
||||||
|
.filter(module => !!module)
|
||||||
|
.map<ToolbarActions>(module => module.config.actions)
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
const combined = combine(actions, context);
|
||||||
|
|
||||||
const ordered = orderBy(
|
const ordered = orderBy(
|
||||||
combined,
|
combined,
|
||||||
@@ -194,40 +218,36 @@ export function renderToolbar(
|
|||||||
context,
|
context,
|
||||||
renderMenuActionItem
|
renderMenuActionItem
|
||||||
);
|
);
|
||||||
if (moreMenuItems.length) {
|
// if (moreMenuItems.length) {
|
||||||
// TODO(@fundon): edgeless case needs to be considered
|
// TODO(@fundon): edgeless case needs to be considered
|
||||||
const key = `${flavour}:${context.getCurrentModelBy(BlockSelection)?.id}`;
|
const key = `${context.getCurrentModel()?.id ?? context.getCurrentElement()?.id}`;
|
||||||
|
|
||||||
primaryActionGroup.push({
|
primaryActionGroup.push({
|
||||||
id: 'more',
|
id: 'more',
|
||||||
content: html`${keyed(
|
content: html`${keyed(
|
||||||
key,
|
`${flavour}:${key}`,
|
||||||
html`
|
html`
|
||||||
<editor-menu-button
|
<editor-menu-button
|
||||||
class="more-menu"
|
class="more-menu"
|
||||||
.contentPadding="${'8px'}"
|
.contentPadding="${'8px'}"
|
||||||
.button=${html`
|
.button=${html`
|
||||||
<editor-icon-button aria-label="More" .tooltip="${'More'}">
|
<editor-icon-button aria-label="More" .tooltip="${'More'}">
|
||||||
${MoreVerticalIcon()}
|
${MoreVerticalIcon()}
|
||||||
</editor-icon-button>
|
</editor-icon-button>
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div data-size="large" data-orientation="vertical">
|
<div data-size="large" data-orientation="vertical">
|
||||||
${join(moreMenuItems, () =>
|
${join(moreMenuItems, renderToolbarSeparator('horizontal'))}
|
||||||
renderToolbarSeparator('horizontal')
|
</div>
|
||||||
)}
|
</editor-menu-button>
|
||||||
</div>
|
`
|
||||||
</editor-menu-button>
|
)}`,
|
||||||
`
|
});
|
||||||
)}`,
|
// }
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
join(renderActions(primaryActionGroup, context), () =>
|
join(renderActions(primaryActionGroup, context), renderToolbarSeparator()),
|
||||||
renderToolbarSeparator()
|
|
||||||
),
|
|
||||||
toolbar
|
toolbar
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,14 +217,18 @@ function createToolbarMoreMenuConfigV2(baseUrl?: string) {
|
|||||||
id: 'copy-as-image',
|
id: 'copy-as-image',
|
||||||
label: 'Copy as Image',
|
label: 'Copy as Image',
|
||||||
icon: CopyAsImgaeIcon(),
|
icon: CopyAsImgaeIcon(),
|
||||||
when: ({ isEdgelessMode, gfx }) =>
|
when: ({ isEdgelessMode, gfx, flags }) =>
|
||||||
isEdgelessMode && gfx.selection.selectedElements.length > 0,
|
!flags.isHovering() &&
|
||||||
|
isEdgelessMode &&
|
||||||
|
gfx.selection.selectedElements.length > 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'copy-link-to-block',
|
id: 'copy-link-to-block',
|
||||||
label: 'Copy link to block',
|
label: 'Copy link to block',
|
||||||
icon: LinkIcon(),
|
icon: LinkIcon(),
|
||||||
when: ({ isPageMode, selection, gfx }) => {
|
when: ({ isPageMode, selection, gfx, flags }) => {
|
||||||
|
if (flags.isHovering()) return false;
|
||||||
|
|
||||||
const items = selection
|
const items = selection
|
||||||
.getGroup('note')
|
.getGroup('note')
|
||||||
.filter(item =>
|
.filter(item =>
|
||||||
@@ -394,7 +398,7 @@ function createToolbarMoreMenuConfigV2(baseUrl?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createExternalLinkableToolbarConfig(
|
function createExternalLinkableToolbarConfig(
|
||||||
kclass:
|
klass:
|
||||||
| typeof BookmarkBlockComponent
|
| typeof BookmarkBlockComponent
|
||||||
| typeof EmbedFigmaBlockComponent
|
| typeof EmbedFigmaBlockComponent
|
||||||
| typeof EmbedGithubBlockComponent
|
| typeof EmbedGithubBlockComponent
|
||||||
@@ -411,10 +415,7 @@ function createExternalLinkableToolbarConfig(
|
|||||||
tooltip: 'Copy link',
|
tooltip: 'Copy link',
|
||||||
icon: CopyIcon(),
|
icon: CopyIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockComponentBy(
|
const model = ctx.getCurrentBlockComponentBy(klass)?.model;
|
||||||
BlockSelection,
|
|
||||||
kclass
|
|
||||||
)?.model;
|
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
const { url } = model.props;
|
const { url } = model.props;
|
||||||
@@ -439,10 +440,7 @@ function createExternalLinkableToolbarConfig(
|
|||||||
tooltip: 'Edit',
|
tooltip: 'Edit',
|
||||||
icon: EditIcon(),
|
icon: EditIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(klass);
|
||||||
BlockSelection,
|
|
||||||
kclass
|
|
||||||
);
|
|
||||||
if (!component) return;
|
if (!component) return;
|
||||||
|
|
||||||
ctx.hide();
|
ctx.hide();
|
||||||
@@ -516,7 +514,7 @@ function createOpenDocActionGroup(
|
|||||||
id: 'A.open-doc',
|
id: 'A.open-doc',
|
||||||
actions: openDocActions,
|
actions: openDocActions,
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(BlockSelection, klass);
|
const component = ctx.getCurrentBlockComponentBy(klass);
|
||||||
if (!component) return null;
|
if (!component) return null;
|
||||||
|
|
||||||
const actions = this.actions
|
const actions = this.actions
|
||||||
@@ -624,7 +622,6 @@ const embedLinkedDocToolbarConfig = {
|
|||||||
icon: EditIcon(),
|
icon: EditIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedLinkedDocBlockComponent
|
EmbedLinkedDocBlockComponent
|
||||||
);
|
);
|
||||||
if (!component) return;
|
if (!component) return;
|
||||||
@@ -717,7 +714,6 @@ const embedSyncedDocToolbarConfig = {
|
|||||||
icon: EditIcon(),
|
icon: EditIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedSyncedDocBlockComponent
|
EmbedSyncedDocBlockComponent
|
||||||
);
|
);
|
||||||
if (!component) return;
|
if (!component) return;
|
||||||
@@ -922,7 +918,6 @@ const embedIframeToolbarConfig = {
|
|||||||
icon: CopyIcon(),
|
icon: CopyIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentBlockComponentBy(
|
const model = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedIframeBlockComponent
|
EmbedIframeBlockComponent
|
||||||
)?.model;
|
)?.model;
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
@@ -950,7 +945,6 @@ const embedIframeToolbarConfig = {
|
|||||||
icon: EditIcon(),
|
icon: EditIcon(),
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const component = ctx.getCurrentBlockComponentBy(
|
const component = ctx.getCurrentBlockComponentBy(
|
||||||
BlockSelection,
|
|
||||||
EmbedIframeBlockComponent
|
EmbedIframeBlockComponent
|
||||||
);
|
);
|
||||||
if (!component) return;
|
if (!component) return;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
dragBetweenIndices,
|
dragBetweenIndices,
|
||||||
enterPlaygroundRoom,
|
enterPlaygroundRoom,
|
||||||
focusRichText,
|
focusRichText,
|
||||||
|
focusRichTextEnd,
|
||||||
focusTitle,
|
focusTitle,
|
||||||
getBoundingBox,
|
getBoundingBox,
|
||||||
getEditorHostLocator,
|
getEditorHostLocator,
|
||||||
@@ -330,13 +331,22 @@ test('should format quick bar be able to link text', async ({
|
|||||||
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
||||||
await expect(linkPopoverInput).toBeVisible();
|
await expect(linkPopoverInput).toBeVisible();
|
||||||
|
|
||||||
await type(page, 'https://www.example.com');
|
const url = 'https://www.example.com';
|
||||||
|
|
||||||
|
await type(page, url);
|
||||||
await pressEnter(page);
|
await pressEnter(page);
|
||||||
|
|
||||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||||
`${testInfo.title}_init.json`
|
`${testInfo.title}_init.json`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const linkLocator = page.locator('affine-link a');
|
||||||
|
await expect(linkLocator).toHaveAttribute('href', url);
|
||||||
|
|
||||||
|
await focusRichTextEnd(page);
|
||||||
|
|
||||||
|
await dragBetweenIndices(page, [1, 3], [1, 0]);
|
||||||
|
|
||||||
// The link button should be active after click
|
// The link button should be active after click
|
||||||
await expect(linkBtn).toHaveAttribute('active', '');
|
await expect(linkBtn).toHaveAttribute('active', '');
|
||||||
await linkBtn.click();
|
await linkBtn.click();
|
||||||
|
|||||||
Reference in New Issue
Block a user