feat(editor): add toolbar registry extension (#9572)

### What's Changed!

#### Added
Manage various types of toolbars uniformly in one place.

* `affine-toolbar-widget`
* `ToolbarRegistryExtension`

The toolbar currently supports and handles several scenarios:

1.  Select blocks: `BlockSelection`
2. Select text: `TextSelection` or `NativeSelection`
3. Hover a link: `affine-link` and `affine-reference`

#### Removed
Remove redundant toolbar implementations.

* `attachment` toolbar
* `bookmark` toolbar
* `embed` toolbar
* `formatting` toolbar
* `affine-link` toolbar
* `affine-reference` toolbar

### How to migrate?

Here is an example that can help us migrate some unrefactored toolbars:

Check out the more detailed types of [`ToolbarModuleConfig`](c178debf2d/blocksuite/affine/shared/src/services/toolbar-service/config.ts).

1.  Add toolbar configuration file to a block type, such as bookmark block: [`config.ts`](c178debf2d/blocksuite/affine/block-bookmark/src/configs/toolbar.ts)

```ts
export const builtinToolbarConfig = {
  actions: [
    {
      id: 'a.preview',
      content(ctx) {
        const model = ctx.getCurrentModelBy(BlockSelection, BookmarkBlockModel);
        if (!model) return null;

        const { url } = model;

        return html`<affine-link-preview .url=${url}></affine-link-preview>`;
      },
    },
    {
      id: 'b.conversions',
      actions: [
        {
          id: 'inline',
          label: 'Inline view',
          run(ctx) {
          },
        },
        {
          id: 'card',
          label: 'Card view',
          disabled: true,
        },
        {
          id: 'embed',
          label: 'Embed view',
          disabled(ctx) {
          },
          run(ctx) {
          },
        },
      ],
      content(ctx) {
      },
    } satisfies ToolbarActionGroup<ToolbarAction>,
    {
      id: 'c.style',
      actions: [
        {
          id: 'horizontal',
          label: 'Large horizontal style',
        },
        {
          id: 'list',
          label: 'Small horizontal style',
        },
      ],
      content(ctx) {
      },
    } satisfies ToolbarActionGroup<ToolbarAction>,
    {
      id: 'd.caption',
      tooltip: 'Caption',
      icon: CaptionIcon(),
      run(ctx) {
      },
    },
    {
      placement: ActionPlacement.More,
      id: 'a.clipboard',
      actions: [
        {
          id: 'copy',
          label: 'Copy',
          icon: CopyIcon(),
          run(ctx) {
          },
        },
        {
          id: 'duplicate',
          label: 'Duplicate',
          icon: DuplicateIcon(),
          run(ctx) {
          },
        },
      ],
    },
    {
      placement: ActionPlacement.More,
      id: 'b.refresh',
      label: 'Reload',
      icon: ResetIcon(),
      run(ctx) {
      },
    },
    {
      placement: ActionPlacement.More,
      id: 'c.delete',
      label: 'Delete',
      icon: DeleteIcon(),
      variant: 'destructive',
      run(ctx) {
      },
    },
  ],
} as const satisfies ToolbarModuleConfig;
```

2. Add configuration extension to a block spec: [bookmark's spec](c178debf2d/blocksuite/affine/block-bookmark/src/bookmark-spec.ts)

```ts
const flavour = BookmarkBlockSchema.model.flavour;

export const BookmarkBlockSpec: ExtensionType[] = [
  ...,
  ToolbarModuleExtension({
    id: BlockFlavourIdentifier(flavour),
    config: builtinToolbarConfig,
  }),
].flat();
```

3. If the bock type already has a toolbar configuration built in, we can customize it in the following ways:

Check out the [editor's config](c178debf2d/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts (L51C4-L54C8)) file.

```ts
// Defines a toolbar configuration for the bookmark block type
const customBookmarkToolbarConfig = {
  actions: [
    ...
  ]
} as const satisfies ToolbarModuleConfig;

// Adds it into the editor's config
 ToolbarModuleExtension({
    id: BlockFlavourIdentifier('custom:affine:bookmark'),
    config: customBookmarkToolbarConfig,
 }),
```

4. If we want to extend the global:

```ts
// Defines a toolbar configuration
const customWildcardToolbarConfig = {
  actions: [
    ...
  ]
} as const satisfies ToolbarModuleConfig;

// Adds it into the editor's config
 ToolbarModuleExtension({
    id: BlockFlavourIdentifier('custom:affine:*'),
    config: customWildcardToolbarConfig,
 }),
```

Currently, only most toolbars in page mode have been refactored. Next is edgeless mode.
This commit is contained in:
fundon
2025-03-06 06:46:03 +00:00
parent 06e4bd9aed
commit ec9bd1f383
147 changed files with 6389 additions and 5156 deletions

View File

@@ -2239,12 +2239,12 @@ __metadata:
"@blocksuite/icons": "npm:^2.2.1"
"@blocksuite/inline": "workspace:*"
"@blocksuite/store": "workspace:*"
"@floating-ui/dom": "npm:^1.6.13"
"@lit/context": "npm:^1.1.2"
"@preact/signals-core": "npm:^1.8.0"
"@toeverything/theme": "npm:^1.1.12"
lit: "npm:^3.2.0"
minimatch: "npm:^10.0.1"
yjs: "npm:^13.6.23"
zod: "npm:^3.23.8"
languageName: unknown
linkType: soft
@@ -2524,17 +2524,18 @@ __metadata:
version: 0.0.0-use.local
resolution: "@blocksuite/affine-block-note@workspace:blocksuite/affine/block-note"
dependencies:
"@blocksuite/affine-block-database": "workspace:*"
"@blocksuite/affine-block-embed": "workspace:*"
"@blocksuite/affine-block-surface": "workspace:*"
"@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-shared": "workspace:*"
"@blocksuite/block-std": "workspace:*"
"@blocksuite/data-view": "workspace:*"
"@blocksuite/global": "workspace:*"
"@blocksuite/icons": "npm:^2.2.1"
"@blocksuite/inline": "workspace:*"
"@blocksuite/store": "workspace:*"
"@floating-ui/dom": "npm:^1.6.13"
"@lit/context": "npm:^1.1.2"
"@preact/signals-core": "npm:^1.8.0"
"@toeverything/theme": "npm:^1.1.12"
@@ -2595,6 +2596,7 @@ __metadata:
"@blocksuite/affine-widget-frame-title": "workspace:*"
"@blocksuite/affine-widget-remote-selection": "workspace:*"
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*"
"@blocksuite/affine-widget-toolbar": "workspace:*"
"@blocksuite/block-std": "workspace:*"
"@blocksuite/data-view": "workspace:*"
"@blocksuite/global": "workspace:*"
@@ -2932,6 +2934,32 @@ __metadata:
languageName: unknown
linkType: soft
"@blocksuite/affine-widget-toolbar@workspace:*, @blocksuite/affine-widget-toolbar@workspace:blocksuite/affine/widget-toolbar":
version: 0.0.0-use.local
resolution: "@blocksuite/affine-widget-toolbar@workspace:blocksuite/affine/widget-toolbar"
dependencies:
"@blocksuite/affine-block-database": "workspace:*"
"@blocksuite/affine-block-table": "workspace:*"
"@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-shared": "workspace:*"
"@blocksuite/block-std": "workspace:*"
"@blocksuite/global": "workspace:*"
"@blocksuite/icons": "npm:^2.2.1"
"@floating-ui/dom": "npm:^1.6.13"
"@preact/signals-core": "npm:^1.8.0"
"@toeverything/theme": "npm:^1.1.12"
"@types/lodash-es": "npm:^4.17.12"
"@types/lodash.groupby": "npm:^4"
"@types/lodash.mergewith": "npm:^4"
"@types/lodash.orderby": "npm:^4"
"@types/lodash.partition": "npm:^4"
"@types/lodash.topairs": "npm:^4"
lit: "npm:^3.2.0"
lodash-es: "npm:^4.17.21"
languageName: unknown
linkType: soft
"@blocksuite/affine@workspace:*, @blocksuite/affine@workspace:blocksuite/affine/all":
version: 0.0.0-use.local
resolution: "@blocksuite/affine@workspace:blocksuite/affine/all"
@@ -3007,6 +3035,7 @@ __metadata:
"@blocksuite/affine-widget-frame-title": "workspace:*"
"@blocksuite/affine-widget-remote-selection": "workspace:*"
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*"
"@blocksuite/affine-widget-toolbar": "workspace:*"
"@blocksuite/block-std": "workspace:*"
"@blocksuite/data-view": "workspace:*"
"@blocksuite/global": "workspace:*"
@@ -3127,7 +3156,7 @@ __metadata:
"@blocksuite/integration-test": "workspace:*"
"@blocksuite/store": "workspace:*"
"@playwright/test": "npm:=1.50.1"
"@toeverything/theme": "npm:^1.1.7"
"@toeverything/theme": "npm:^1.1.12"
languageName: unknown
linkType: soft
@@ -13500,7 +13529,7 @@ __metadata:
languageName: node
linkType: hard
"@toeverything/theme@npm:^1.1.12, @toeverything/theme@npm:^1.1.7":
"@toeverything/theme@npm:^1.1.12":
version: 1.1.12
resolution: "@toeverything/theme@npm:1.1.12"
checksum: 10/32247cf7f7a819445fa7373a3afafaafc6a8527b95934e37e5f2c32181224b2184691b24bd8a400872cca7cd1336efe98bd5ad3366b98d149e48b3baee2609a7
@@ -14134,6 +14163,15 @@ __metadata:
languageName: node
linkType: hard
"@types/lodash.groupby@npm:^4":
version: 4.6.9
resolution: "@types/lodash.groupby@npm:4.6.9"
dependencies:
"@types/lodash": "npm:*"
checksum: 10/b8310a9f89badc42a504887ca0b9619c2a284b3fec8dc505cf72508eb6beba47b822df939c7d57c0f69bc685f51ff5a232e0480ecad6b18b7ab76fecc1d74691
languageName: node
linkType: hard
"@types/lodash.ismatch@npm:^4.4.9":
version: 4.4.9
resolution: "@types/lodash.ismatch@npm:4.4.9"
@@ -14152,6 +14190,42 @@ __metadata:
languageName: node
linkType: hard
"@types/lodash.mergewith@npm:^4":
version: 4.6.9
resolution: "@types/lodash.mergewith@npm:4.6.9"
dependencies:
"@types/lodash": "npm:*"
checksum: 10/c5a67e83040103decfd37090127118f5758773d0ce2a1756d442b371721737c7752f48f62544cc970f44abec8471f260cc4c844e1a4fdef8b76cb96bdec8a595
languageName: node
linkType: hard
"@types/lodash.orderby@npm:^4":
version: 4.6.9
resolution: "@types/lodash.orderby@npm:4.6.9"
dependencies:
"@types/lodash": "npm:*"
checksum: 10/f3ca3a6ba09caef431955f3110a6704e25c90934fb70973e2ed1f4dd9aa48c4776c7e8b909b78f9ae1fb169e437497f8fe8e60fafb5c74ede49c36739ac44c3e
languageName: node
linkType: hard
"@types/lodash.partition@npm:^4":
version: 4.6.9
resolution: "@types/lodash.partition@npm:4.6.9"
dependencies:
"@types/lodash": "npm:*"
checksum: 10/29d1fead7f9a71af8279ceb6c1b08128c7f05f06a72ed60fea02a7c010f69006a4e3dc24afb9c4a3187a033b918a1b3dcf611004781f927ff573b104bdf34060
languageName: node
linkType: hard
"@types/lodash.topairs@npm:^4":
version: 4.3.9
resolution: "@types/lodash.topairs@npm:4.3.9"
dependencies:
"@types/lodash": "npm:*"
checksum: 10/8d4d9cb7be1c333b70b8d035d0673ef41158bfe24e06616046879149a15438af89673fe21abe1b1e62ddbc00eae5fe4c3795649de8a98742771ba8949100a300
languageName: node
linkType: hard
"@types/lodash@npm:*":
version: 4.17.15
resolution: "@types/lodash@npm:4.17.15"