From 7f4b56e05cc20ca20e4bde0a24cadbef7c9a1657 Mon Sep 17 00:00:00 2001 From: fundon Date: Wed, 19 Mar 2025 04:05:36 +0000 Subject: [PATCH] refactor(editor): edgeless external embed card toolbar config extension (#10712) --- .../blocks/block-embed/src/configs/toolbar.ts | 380 +++++++++++++++--- .../src/embed-figma-block/embed-figma-spec.ts | 14 +- .../embed-github-block/embed-github-spec.ts | 14 +- .../src/embed-loom-block/embed-loom-spec.ts | 14 +- .../embed-youtube-block/embed-youtube-spec.ts | 14 +- .../src/edgeless/configs/toolbar/embed.ts | 11 - .../src/edgeless/configs/toolbar/index.ts | 6 - .../affine/model/src/blocks/embed/types.ts | 5 +- .../src/services/toolbar-service/context.ts | 40 +- .../widgets/widget-toolbar/src/toolbar.ts | 37 +- .../extensions/editor-config/toolbar/index.ts | 40 +- 11 files changed, 415 insertions(+), 160 deletions(-) delete mode 100644 blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/embed.ts diff --git a/blocksuite/affine/blocks/block-embed/src/configs/toolbar.ts b/blocksuite/affine/blocks/block-embed/src/configs/toolbar.ts index 4292519f32..74735d6f2b 100644 --- a/blocksuite/affine/blocks/block-embed/src/configs/toolbar.ts +++ b/blocksuite/affine/blocks/block-embed/src/configs/toolbar.ts @@ -1,18 +1,29 @@ +import { reassociateConnectorsCommand } from '@blocksuite/affine-block-surface'; import { toast } from '@blocksuite/affine-components/toast'; import { BookmarkStyles, + type EmbedCardStyle, EmbedGithubModel, + EmbedGithubStyles, isExternalEmbedModel, } from '@blocksuite/affine-model'; +import { + EMBED_CARD_HEIGHT, + EMBED_CARD_WIDTH, +} from '@blocksuite/affine-shared/consts'; import { ActionPlacement, EmbedOptionProvider, + type LinkEventType, type ToolbarAction, type ToolbarActionGroup, + type ToolbarContext, type ToolbarModuleConfig, + ToolbarModuleExtension, } from '@blocksuite/affine-shared/services'; import { getBlockProps } from '@blocksuite/affine-shared/utils'; -import { BlockSelection } from '@blocksuite/block-std'; +import { BlockFlavourIdentifier, BlockSelection } from '@blocksuite/block-std'; +import { Bound } from '@blocksuite/global/gfx'; import { CaptionIcon, CopyIcon, @@ -20,8 +31,8 @@ import { DuplicateIcon, ResetIcon, } from '@blocksuite/icons/lit'; -import { Slice, Text } from '@blocksuite/store'; -import { signal } from '@preact/signals-core'; +import { type ExtensionType, Slice, Text } from '@blocksuite/store'; +import { computed, signal } from '@preact/signals-core'; import { html } from 'lit'; import { keyed } from 'lit/directives/keyed.js'; import * as Y from 'yjs'; @@ -32,15 +43,46 @@ import type { EmbedLoomBlockComponent } from '../embed-loom-block'; import type { EmbedYoutubeBlockComponent } from '../embed-youtube-block'; const trackBaseProps = { - segment: 'doc', - page: 'doc editor', - module: 'toolbar', category: 'link', type: 'card view', }; +const previewAction = { + id: 'a.preview', + content(ctx) { + const model = ctx.getCurrentModel(); + if (!model || !isExternalEmbedModel(model)) return null; + + const { url } = model.props; + const options = ctx.std.get(EmbedOptionProvider).getEmbedBlockOptions(url); + + if (options?.viewType !== 'card') return null; + + return html``; + }, +} satisfies ToolbarAction; + +const createOnToggleFn = + ( + ctx: ToolbarContext, + name: Extract< + LinkEventType, + | 'OpenedViewSelector' + | 'OpenedCardStyleSelector' + | 'OpenedCardScaleSelector' + >, + control: 'switch view' | 'switch card style' | 'switch card scale' + ) => + (e: CustomEvent) => { + e.stopPropagation(); + const opened = e.detail; + if (!opened) return; + + ctx.track(name, { ...trackBaseProps, control }); + }; + // External embed blocks -export function createBuiltinToolbarConfigForExternal( +function createBuiltinToolbarConfigForExternal( klass: | typeof EmbedGithubBlockComponent | typeof EmbedFigmaBlockComponent @@ -49,22 +91,7 @@ export function createBuiltinToolbarConfigForExternal( ) { return { actions: [ - { - id: 'a.preview', - content(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; - if (!model || !isExternalEmbedModel(model)) return null; - - const { url } = model.props; - const options = ctx.std - .get(EmbedOptionProvider) - .getEmbedBlockOptions(url); - - if (options?.viewType !== 'card') return null; - - return html``; - }, - }, + previewAction, { id: 'b.conversions', actions: [ @@ -72,7 +99,7 @@ export function createBuiltinToolbarConfigForExternal( id: 'inline', label: 'Inline view', run(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; + const model = ctx.getCurrentModel(); if (!model || !isExternalEmbedModel(model)) return; const { title, caption, url: link } = model.props; @@ -105,7 +132,7 @@ export function createBuiltinToolbarConfigForExternal( id: 'card', label: 'Card view', disabled(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; + const model = ctx.getCurrentModel(); if (!model || !isExternalEmbedModel(model)) return true; const { url } = model.props; @@ -116,7 +143,7 @@ export function createBuiltinToolbarConfigForExternal( return options?.viewType === 'card'; }, run(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; + const model = ctx.getCurrentModel(); if (!model || !isExternalEmbedModel(model)) return; const { url, caption } = model.props; @@ -165,7 +192,7 @@ export function createBuiltinToolbarConfigForExternal( id: 'embed', label: 'Embed view', disabled(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; + const model = ctx.getCurrentModel(); if (!model || !isExternalEmbedModel(model)) return false; const { url } = model.props; @@ -176,7 +203,7 @@ export function createBuiltinToolbarConfigForExternal( return options?.viewType === 'embed'; }, when(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; + const model = ctx.getCurrentModel(); if (!model || !isExternalEmbedModel(model)) return false; const { url } = model.props; @@ -187,7 +214,7 @@ export function createBuiltinToolbarConfigForExternal( return options?.viewType === 'embed'; }, run(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; + const model = ctx.getCurrentModel(); if (!model || !isExternalEmbedModel(model)) return; const { url, caption } = model.props; @@ -231,7 +258,7 @@ export function createBuiltinToolbarConfigForExternal( }, ], content(ctx) { - const model = ctx.getCurrentBlockByType(klass)?.model; + const model = ctx.getCurrentModel(); if (!model || !isExternalEmbedModel(model)) return null; const { url } = model.props; @@ -242,15 +269,11 @@ export function createBuiltinToolbarConfigForExternal( const viewType$ = signal( `${viewType === 'card' ? 'Card' : 'Embed'} view` ); - const onToggle = (e: CustomEvent) => { - const opened = e.detail; - if (!opened) return; - - ctx.track('OpenedViewSelector', { - ...trackBaseProps, - control: 'switch view', - }); - }; + const onToggle = createOnToggleFn( + ctx, + 'OpenedViewSelector', + 'switch view' + ); return html`${keyed( model, @@ -275,6 +298,9 @@ export function createBuiltinToolbarConfigForExternal( label: 'Small horizontal style', }, ], + when(ctx) { + return Boolean(ctx.getCurrentModelByType(EmbedGithubModel)); + }, content(ctx) { const model = ctx.getCurrentModelByType(EmbedGithubModel); if (!model) return null; @@ -291,16 +317,11 @@ export function createBuiltinToolbarConfigForExternal( }); }, })) satisfies ToolbarAction[]; - - const onToggle = (e: CustomEvent) => { - const opened = e.detail; - if (!opened) return; - - ctx.track('OpenedCardStyleSelector', { - ...trackBaseProps, - control: 'switch card style', - }); - }; + const onToggle = createOnToggleFn( + ctx, + 'OpenedCardStyleSelector', + 'switch card style' + ); return html`${keyed( model, @@ -393,3 +414,264 @@ export function createBuiltinToolbarConfigForExternal( ], } as const satisfies ToolbarModuleConfig; } + +const createBuiltinSurfaceToolbarConfigForExternal = ( + klass: + | typeof EmbedGithubBlockComponent + | typeof EmbedFigmaBlockComponent + | typeof EmbedLoomBlockComponent + | typeof EmbedYoutubeBlockComponent +) => { + return { + actions: [ + previewAction, + { + id: 'b.conversions', + actions: [ + { + id: 'card', + label: 'Card view', + run(ctx) { + const model = ctx.getCurrentBlockByType(klass)?.model; + if (!model || !isExternalEmbedModel(model)) return; + + const { id: oldId, xywh, parent } = model; + const { url, caption } = model.props; + + let { style } = model.props; + let flavour = 'affine:bookmark'; + + if (!BookmarkStyles.includes(style)) { + style = BookmarkStyles[0]; + } + + const bounds = Bound.deserialize(xywh); + bounds.w = EMBED_CARD_WIDTH[style]; + bounds.h = EMBED_CARD_HEIGHT[style]; + + const newId = ctx.store.addBlock( + flavour, + { url, caption, style, xywh: bounds.serialize() }, + parent + ); + + ctx.command.exec(reassociateConnectorsCommand, { oldId, newId }); + + ctx.store.deleteBlock(model); + + // Selects new block + ctx.gfx.selection.set({ editing: false, elements: [newId] }); + + ctx.track('SelectedView', { + ...trackBaseProps, + control: 'select view', + type: 'card view', + }); + }, + }, + { + id: 'embed', + label: 'Embed view', + disabled: true, + }, + ], + when(ctx) { + const model = ctx.getCurrentBlockByType(klass)?.model; + if (!model || !isExternalEmbedModel(model)) return false; + + const { url } = model.props; + const options = ctx.std + .get(EmbedOptionProvider) + .getEmbedBlockOptions(url); + + return options?.viewType === 'embed'; + }, + content(ctx) { + const model = ctx.getCurrentBlockByType(klass)?.model; + if (!model || !isExternalEmbedModel(model)) return null; + + const { url } = model.props; + const viewType = + ctx.std.get(EmbedOptionProvider).getEmbedBlockOptions(url) + ?.viewType ?? 'card'; + const actions = this.actions.map(action => ({ ...action })); + const viewType$ = signal( + `${viewType === 'card' ? 'Card' : 'Embed'} view` + ); + const onToggle = createOnToggleFn( + ctx, + 'OpenedViewSelector', + 'switch view' + ); + + return html`${keyed( + model, + html`` + )}`; + }, + } satisfies ToolbarActionGroup, + { + id: 'c.style', + actions: [ + { + id: 'horizontal', + label: 'Large horizontal style', + }, + { + id: 'list', + label: 'Small horizontal style', + }, + { + id: 'vertical', + label: 'Large vertical style', + }, + { + id: 'cube', + label: 'Small vertical style', + }, + ].filter(action => + EmbedGithubStyles.includes(action.id as EmbedCardStyle) + ), + when(ctx) { + return Boolean(ctx.getCurrentModelByType(EmbedGithubModel)); + }, + content(ctx) { + const model = ctx.getCurrentBlockByType(klass)?.model; + if (!model) return null; + + const actions = this.actions.map(action => ({ + ...action, + run: ({ store }) => { + const style = action.id as EmbedCardStyle; + const bounds = Bound.deserialize(model.xywh); + bounds.w = EMBED_CARD_WIDTH[style]; + bounds.h = EMBED_CARD_HEIGHT[style]; + const xywh = bounds.serialize(); + + store.updateBlock(model, { style, xywh }); + + ctx.track('SelectedCardStyle', { + ...trackBaseProps, + control: 'select card style', + type: style, + }); + }, + })) satisfies ToolbarAction[]; + const style$ = model.props.style$; + const onToggle = createOnToggleFn( + ctx, + 'OpenedCardStyleSelector', + 'switch card style' + ); + + return html`${keyed( + model, + html`` + )}`; + }, + } satisfies ToolbarActionGroup, + { + id: 'd.caption', + tooltip: 'Caption', + icon: CaptionIcon(), + run(ctx) { + const block = ctx.getCurrentBlockByType(klass); + block?.captionEditor?.show(); + + ctx.track('OpenedCaptionEditor', { + ...trackBaseProps, + control: 'add caption', + }); + }, + }, + { + id: 'e.scale', + content(ctx) { + const model = ctx.getCurrentBlockByType(klass)?.model; + if (!model) return null; + + const scale$ = computed(() => { + const { + xywh$: { value: xywh }, + } = model; + const { + style$: { value: style }, + } = model.props; + const bounds = Bound.deserialize(xywh); + const height = EMBED_CARD_HEIGHT[style]; + return Math.round(100 * (bounds.h / height)); + }); + const onSelect = (e: CustomEvent) => { + e.stopPropagation(); + + const scale = e.detail / 100; + + const bounds = Bound.deserialize(model.xywh); + const style = model.props.style; + bounds.h = EMBED_CARD_HEIGHT[style] * scale; + bounds.w = EMBED_CARD_WIDTH[style] * scale; + const xywh = bounds.serialize(); + + ctx.store.updateBlock(model, { xywh }); + + ctx.track('SelectedCardScale', { + ...trackBaseProps, + control: 'select card scale', + }); + }; + const onToggle = createOnToggleFn( + ctx, + 'OpenedCardScaleSelector', + 'switch card scale' + ); + const format = (value: number) => `${value}%`; + + return html`${keyed( + model, + html`` + )}`; + }, + }, + ], + + when: ctx => ctx.getSurfaceBlocksByType(klass).length === 1, + } as const satisfies ToolbarModuleConfig; +}; + +export const createBuiltinToolbarConfigExtension = ( + flavour: string, + klass: + | typeof EmbedGithubBlockComponent + | typeof EmbedFigmaBlockComponent + | typeof EmbedLoomBlockComponent + | typeof EmbedYoutubeBlockComponent +): ExtensionType[] => { + const name = flavour.split(':').pop(); + + return [ + ToolbarModuleExtension({ + id: BlockFlavourIdentifier(flavour), + config: createBuiltinToolbarConfigForExternal(klass), + }), + + ToolbarModuleExtension({ + id: BlockFlavourIdentifier(`affine:surface:${name}`), + config: createBuiltinSurfaceToolbarConfigForExternal(klass), + }), + ]; +}; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-figma-block/embed-figma-spec.ts b/blocksuite/affine/blocks/block-embed/src/embed-figma-block/embed-figma-spec.ts index 330c57c9d2..a41da4f259 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-figma-block/embed-figma-spec.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-figma-block/embed-figma-spec.ts @@ -1,15 +1,10 @@ import { EmbedFigmaBlockSchema } from '@blocksuite/affine-model'; -import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services'; import { SlashMenuConfigExtension } from '@blocksuite/affine-widget-slash-menu'; -import { - BlockServiceIdentifier, - BlockViewExtension, - FlavourExtension, -} from '@blocksuite/block-std'; +import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { literal } from 'lit/static-html.js'; -import { createBuiltinToolbarConfigForExternal } from '../configs/toolbar'; +import { createBuiltinToolbarConfigExtension } from '../configs/toolbar'; import { EmbedFigmaBlockAdapterExtensions } from './adapters/extension'; import { embedFigmaSlashMenuConfig } from './configs/slash-menu'; import { EmbedFigmaBlockComponent } from './embed-figma-block'; @@ -26,9 +21,6 @@ export const EmbedFigmaBlockSpec: ExtensionType[] = [ }), EmbedFigmaBlockAdapterExtensions, EmbedFigmaBlockOptionConfig, - ToolbarModuleExtension({ - id: BlockServiceIdentifier(flavour), - config: createBuiltinToolbarConfigForExternal(EmbedFigmaBlockComponent), - }), + createBuiltinToolbarConfigExtension(flavour, EmbedFigmaBlockComponent), SlashMenuConfigExtension(flavour, embedFigmaSlashMenuConfig), ].flat(); diff --git a/blocksuite/affine/blocks/block-embed/src/embed-github-block/embed-github-spec.ts b/blocksuite/affine/blocks/block-embed/src/embed-github-block/embed-github-spec.ts index 67b1e57fde..1f97d360d6 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-github-block/embed-github-spec.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-github-block/embed-github-spec.ts @@ -1,15 +1,10 @@ import { EmbedGithubBlockSchema } from '@blocksuite/affine-model'; -import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services'; import { SlashMenuConfigExtension } from '@blocksuite/affine-widget-slash-menu'; -import { - BlockServiceIdentifier, - BlockViewExtension, - FlavourExtension, -} from '@blocksuite/block-std'; +import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { literal } from 'lit/static-html.js'; -import { createBuiltinToolbarConfigForExternal } from '../configs/toolbar'; +import { createBuiltinToolbarConfigExtension } from '../configs/toolbar'; import { EmbedGithubBlockAdapterExtensions } from './adapters/extension'; import { embedGithubSlashMenuConfig } from './configs/slash-menu'; import { EmbedGithubBlockComponent } from './embed-github-block'; @@ -30,9 +25,6 @@ export const EmbedGithubBlockSpec: ExtensionType[] = [ }), EmbedGithubBlockAdapterExtensions, EmbedGithubBlockOptionConfig, - ToolbarModuleExtension({ - id: BlockServiceIdentifier(flavour), - config: createBuiltinToolbarConfigForExternal(EmbedGithubBlockComponent), - }), + createBuiltinToolbarConfigExtension(flavour, EmbedGithubBlockComponent), SlashMenuConfigExtension(flavour, embedGithubSlashMenuConfig), ].flat(); diff --git a/blocksuite/affine/blocks/block-embed/src/embed-loom-block/embed-loom-spec.ts b/blocksuite/affine/blocks/block-embed/src/embed-loom-block/embed-loom-spec.ts index bdf3ae33d7..4edf2d877d 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-loom-block/embed-loom-spec.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-loom-block/embed-loom-spec.ts @@ -1,15 +1,10 @@ import { EmbedLoomBlockSchema } from '@blocksuite/affine-model'; -import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services'; import { SlashMenuConfigExtension } from '@blocksuite/affine-widget-slash-menu'; -import { - BlockServiceIdentifier, - BlockViewExtension, - FlavourExtension, -} from '@blocksuite/block-std'; +import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { literal } from 'lit/static-html.js'; -import { createBuiltinToolbarConfigForExternal } from '../configs/toolbar'; +import { createBuiltinToolbarConfigExtension } from '../configs/toolbar'; import { EmbedLoomBlockAdapterExtensions } from './adapters/extension'; import { embedLoomSlashMenuConfig } from './configs/slash-menu'; import { EmbedLoomBlockComponent } from './embed-loom-block'; @@ -30,9 +25,6 @@ export const EmbedLoomBlockSpec: ExtensionType[] = [ }), EmbedLoomBlockAdapterExtensions, EmbedLoomBlockOptionConfig, - ToolbarModuleExtension({ - id: BlockServiceIdentifier(flavour), - config: createBuiltinToolbarConfigForExternal(EmbedLoomBlockComponent), - }), + createBuiltinToolbarConfigExtension(flavour, EmbedLoomBlockComponent), SlashMenuConfigExtension(flavour, embedLoomSlashMenuConfig), ].flat(); diff --git a/blocksuite/affine/blocks/block-embed/src/embed-youtube-block/embed-youtube-spec.ts b/blocksuite/affine/blocks/block-embed/src/embed-youtube-block/embed-youtube-spec.ts index d40893ffc2..101f79ce4f 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-youtube-block/embed-youtube-spec.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-youtube-block/embed-youtube-spec.ts @@ -1,15 +1,10 @@ import { EmbedYoutubeBlockSchema } from '@blocksuite/affine-model'; -import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services'; import { SlashMenuConfigExtension } from '@blocksuite/affine-widget-slash-menu'; -import { - BlockServiceIdentifier, - BlockViewExtension, - FlavourExtension, -} from '@blocksuite/block-std'; +import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { literal } from 'lit/static-html.js'; -import { createBuiltinToolbarConfigForExternal } from '../configs/toolbar'; +import { createBuiltinToolbarConfigExtension } from '../configs/toolbar'; import { EmbedYoutubeBlockAdapterExtensions } from './adapters/extension'; import { embedYoutubeSlashMenuConfig } from './configs/slash-menu'; import { EmbedYoutubeBlockComponent } from './embed-youtube-block'; @@ -30,9 +25,6 @@ export const EmbedYoutubeBlockSpec: ExtensionType[] = [ }), EmbedYoutubeBlockAdapterExtensions, EmbedYoutubeBlockOptionConfig, - ToolbarModuleExtension({ - id: BlockServiceIdentifier(flavour), - config: createBuiltinToolbarConfigForExternal(EmbedYoutubeBlockComponent), - }), + createBuiltinToolbarConfigExtension(flavour, EmbedYoutubeBlockComponent), SlashMenuConfigExtension('affine:embed-youtube', embedYoutubeSlashMenuConfig), ].flat(); diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/embed.ts b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/embed.ts deleted file mode 100644 index af6fe62867..0000000000 --- a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/embed.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services'; - -export const builtinEmbedToolbarConfig = { - actions: [ - { - id: 'a.test', - label: 'Embed', - run() {}, - }, - ], -} as const satisfies ToolbarModuleConfig; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts index 99242da8cb..3890dc3536 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts @@ -4,7 +4,6 @@ import type { ExtensionType } from '@blocksuite/store'; import { builtinBrushToolbarConfig } from './brush'; import { builtinConnectorToolbarConfig } from './connector'; -import { builtinEmbedToolbarConfig } from './embed'; import { builtinFrameToolbarConfig } from './frame'; import { builtinGroupToolbarConfig } from './group'; import { builtinImageToolbarConfig } from './image'; @@ -30,11 +29,6 @@ export const EdgelessElementToolbarExtension: ExtensionType[] = [ config: builtinConnectorToolbarConfig, }), - ToolbarModuleExtension({ - id: BlockFlavourIdentifier('affine:surface:embed'), - config: builtinEmbedToolbarConfig, - }), - ToolbarModuleExtension({ id: BlockFlavourIdentifier('affine:surface:frame'), config: builtinFrameToolbarConfig, diff --git a/blocksuite/affine/model/src/blocks/embed/types.ts b/blocksuite/affine/model/src/blocks/embed/types.ts index 446588d2db..5ff4cf53f2 100644 --- a/blocksuite/affine/model/src/blocks/embed/types.ts +++ b/blocksuite/affine/model/src/blocks/embed/types.ts @@ -1,3 +1,4 @@ +import type { GfxModel } from '@blocksuite/block-std/gfx'; import type { BlockModel } from '@blocksuite/store'; import { EmbedFigmaModel } from './figma'; @@ -34,7 +35,7 @@ export type LinkableEmbedModel = EmbedCardModel | EmbedIframeBlockModel; export type BuiltInEmbedModel = EmbedCardModel | EmbedHtmlModel; export function isExternalEmbedModel( - model: BlockModel + model: GfxModel | BlockModel ): model is InstanceType { return ( model instanceof EmbedFigmaModel || @@ -45,7 +46,7 @@ export function isExternalEmbedModel( } export function isInternalEmbedModel( - model: BlockModel + model: GfxModel | BlockModel ): model is InstanceType { return ( model instanceof EmbedLinkedDocModel || model instanceof EmbedSyncedDocModel diff --git a/blocksuite/affine/shared/src/services/toolbar-service/context.ts b/blocksuite/affine/shared/src/services/toolbar-service/context.ts index b99862bb8b..6d2426ef0b 100644 --- a/blocksuite/affine/shared/src/services/toolbar-service/context.ts +++ b/blocksuite/affine/shared/src/services/toolbar-service/context.ts @@ -128,8 +128,8 @@ abstract class ToolbarContextBase { ); } - getSurfaceModelsByType any>( - klass: M + getSurfaceModelsByType any>( + klass: T ) { if (this.hasSelectedSurfaceModels) { const elements = this.elementsMap$.peek().get(this.flavour$.peek()); @@ -140,6 +140,20 @@ abstract class ToolbarContextBase { return []; } + getSurfaceBlocksByType any>( + klass: T + ) { + if (this.hasSelectedSurfaceModels) { + const elements = this.elementsMap$.peek().get(this.flavour$.peek()); + if (elements?.length) { + return elements + .map(model => this.gfx.view.get(model.id)) + .filter(block => block && this.matchBlock(block, klass)); + } + } + return []; + } + getCurrentBlockBy(type: T) { const selection = this.selection.find(type); if (!selection) return null; @@ -160,17 +174,17 @@ abstract class ToolbarContextBase { : this.getCurrentBlockBy(BlockSelection); } - getCurrentBlockByType any>( - klass: K + getCurrentBlockByType any>( + klass: T ) { const block = this.getCurrentBlock(); return this.matchBlock(block, klass) ? block : null; } - matchBlock any>( + matchBlock any>( component: GfxElementModelView | BlockComponent | null, - klass: K - ): component is InstanceType { + klass: T + ): component is InstanceType { return component instanceof klass; } @@ -184,23 +198,23 @@ abstract class ToolbarContextBase { return this.store.getBlock(selection.blockId)?.model ?? null; } - getCurrentModel() { + getCurrentModel(): GfxModel | BlockModel | null { return this.hasSelectedSurfaceModels ? this.getCurrentModelBy(SurfaceSelection) : this.getCurrentModelBy(BlockSelection); } - getCurrentModelByType any>( - klass: M + getCurrentModelByType any>( + klass: T ) { const model = this.getCurrentModel(); return this.matchModel(model, klass) ? model : null; } - matchModel any>( + matchModel any>( model: GfxModel | BlockModel | null, - klass: K - ): model is InstanceType { + klass: T + ): model is InstanceType { return model instanceof klass; } diff --git a/blocksuite/affine/widgets/widget-toolbar/src/toolbar.ts b/blocksuite/affine/widgets/widget-toolbar/src/toolbar.ts index c3934a4274..d48452b0a6 100644 --- a/blocksuite/affine/widgets/widget-toolbar/src/toolbar.ts +++ b/blocksuite/affine/widgets/widget-toolbar/src/toolbar.ts @@ -397,23 +397,30 @@ export class AffineToolbarWidget extends WidgetComponent { // `card view` or `embed view` disposables.add( std.view.viewUpdated.subscribe(record => { - if (record.type !== 'block') return; - if (!flags.isBlock()) return; + const hasAddedBlock = + record.type === 'block' && record.method === 'add'; + if (!hasAddedBlock) return; - const blockIds = std.selection - .filter$(BlockSelection) - .peek() - .map(s => s.blockId); + if (flags.isBlock()) { + const blockIds = std.selection + .filter$(BlockSelection) + .peek() + .map(s => s.blockId); + if (blockIds.includes(record.id)) { + batch(() => { + this.setReferenceElementWithBlocks( + blockIds + .map(id => std.view.getBlock(id)) + .filter(block => block !== null) + ); + flags.refresh(Flag.Block); + }); + } + return; + } - 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); - }); + if (flags.isSurface()) { + flags.refresh(Flag.Surface); return; } }) diff --git a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts index 74fe93f43c..e1d8ccc7d1 100644 --- a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts +++ b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts @@ -425,7 +425,6 @@ function createExternalLinkableToolbarConfig( toast(ctx.host, 'Copied link to clipboard'); ctx.track('CopiedLink', { - module: 'toolbar', category: matchModels(model, [BookmarkBlockModel]) ? 'bookmark' : 'link', @@ -462,7 +461,6 @@ function createExternalLinkableToolbarConfig( ); ctx.track('OpenedAliasPopup', { - module: 'toolbar', category: matchModels(model, [BookmarkBlockModel]) ? 'bookmark' : 'link', @@ -600,9 +598,6 @@ const embedLinkedDocToolbarConfig = { toast(ctx.host, 'Copied link to clipboard'); ctx.track('CopiedLink', { - segment: 'doc', - page: 'doc editor', - module: 'toolbar', category: 'linked doc', type: 'card view', control: 'copy link', @@ -648,9 +643,6 @@ const embedLinkedDocToolbarConfig = { ); ctx.track('OpenedAliasPopup', { - segment: 'doc', - page: 'doc editor', - module: 'toolbar', category: 'linked doc', type: 'embed view', control: 'edit', @@ -689,9 +681,6 @@ const embedSyncedDocToolbarConfig = { toast(ctx.host, 'Copied link to clipboard'); ctx.track('CopiedLink', { - segment: 'doc', - page: 'doc editor', - module: 'toolbar', category: 'linked doc', type: 'embed view', control: 'copy link', @@ -730,9 +719,6 @@ const embedSyncedDocToolbarConfig = { ); ctx.track('OpenedAliasPopup', { - segment: 'doc', - page: 'doc editor', - module: 'toolbar', category: 'linked doc', type: 'embed view', control: 'edit', @@ -846,9 +832,6 @@ const inlineReferenceToolbarConfig = { toast(ctx.host, 'Copied link to clipboard'); ctx.track('CopiedLink', { - segment: 'doc', - page: 'doc editor', - module: 'toolbar', category: 'linked doc', type: 'inline view', control: 'copy link', @@ -882,9 +865,6 @@ const inlineReferenceToolbarConfig = { abortController.signal.onabort = () => popover.remove(); ctx.track('OpenedAliasPopup', { - segment: 'doc', - page: 'doc editor', - module: 'toolbar', category: 'linked doc', type: 'inline view', control: 'edit', @@ -992,21 +972,41 @@ export const createCustomToolbarExtension = ( config: createExternalLinkableToolbarConfig(EmbedFigmaBlockComponent), }), + ToolbarModuleExtension({ + id: BlockFlavourIdentifier('custom:affine:surface:embed-figma'), + config: createExternalLinkableToolbarConfig(BookmarkBlockComponent), + }), + ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:embed-github'), config: createExternalLinkableToolbarConfig(EmbedGithubBlockComponent), }), + ToolbarModuleExtension({ + id: BlockFlavourIdentifier('custom:affine:surface:embed-github'), + config: createExternalLinkableToolbarConfig(BookmarkBlockComponent), + }), + ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:embed-loom'), config: createExternalLinkableToolbarConfig(EmbedLoomBlockComponent), }), + ToolbarModuleExtension({ + id: BlockFlavourIdentifier('custom:affine:surface:embed-loom'), + config: createExternalLinkableToolbarConfig(BookmarkBlockComponent), + }), + ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:embed-youtube'), config: createExternalLinkableToolbarConfig(EmbedYoutubeBlockComponent), }), + ToolbarModuleExtension({ + id: BlockFlavourIdentifier('custom:affine:surface:embed-youtube'), + config: createExternalLinkableToolbarConfig(BookmarkBlockComponent), + }), + ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:embed-linked-doc'), config: embedLinkedDocToolbarConfig,