Skip to content

Commit

Permalink
feat(editor): add toolbar registry extension
Browse files Browse the repository at this point in the history
  • Loading branch information
fundon committed Jan 10, 2025
1 parent 46d0ebf commit 69a85b7
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 13 deletions.
24 changes: 18 additions & 6 deletions blocksuite/affine/block-attachment/src/attachment-spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std';
import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
import {
BlockFlavourIdentifier,
BlockViewExtension,
FlavourExtension,
} from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';

import { AttachmentBlockNotionHtmlAdapterExtension } from './adapters/notion-html.js';
import { AttachmentBlockNotionHtmlAdapterExtension } from './adapters/notion-html';
import {
AttachmentBlockService,
AttachmentDropOption,
} from './attachment-service.js';
} from './attachment-service';
import {
AttachmentEmbedConfigExtension,
AttachmentEmbedService,
} from './embed.js';
} from './embed';
import { BuiltinToolbarConfig } from './toolbar';

const Flavour = 'affine:attachment';

export const AttachmentBlockSpec: ExtensionType[] = [
FlavourExtension('affine:attachment'),
FlavourExtension(Flavour),
AttachmentBlockService,
BlockViewExtension('affine:attachment', model => {
BlockViewExtension(Flavour, model => {
return model.parent?.flavour === 'affine:surface'
? literal`affine-edgeless-attachment`
: literal`affine-attachment`;
Expand All @@ -24,4 +32,8 @@ export const AttachmentBlockSpec: ExtensionType[] = [
AttachmentEmbedConfigExtension(),
AttachmentEmbedService,
AttachmentBlockNotionHtmlAdapterExtension,
ToolbarModuleExtension({
id: BlockFlavourIdentifier(Flavour),
config: BuiltinToolbarConfig,
}),
];
81 changes: 81 additions & 0 deletions blocksuite/affine/block-attachment/src/toolbar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type {
ToolbarActionGroup,
ToolbarModuleConfig,
} from '@blocksuite/affine-shared/services';

export const BuiltinToolbarConfig = {
actions: [
{
id: 'rename',
tooltip: 'Rename',
run(_cx) {},
},
{
id: 'switch-view',
actions: [
{
id: 'card-view',
label: 'Card view',
run(_cx) {},
},
{
id: 'embed-view',
label: 'Embed view',
run(_cx) {},
},
],
content(_cx) {
this.actions;
return null;
},
} satisfies ToolbarActionGroup,
{
id: 'download',
tooltip: 'Download',
run(_cx) {},
},
{
id: 'caption',
tooltip: 'Caption',
run(_cx) {},
},
{
id: 'clipboard',
placement: 'more',
actions: [
{
id: 'copy',
label: 'Copy',
run(_cx) {},
},
{
id: 'duplicate',
label: 'Duplicate',
run(_cx) {},
},
],
},
{
id: 'refresh',
placement: 'more',
actions: [
{
id: 'reload',
label: 'Reload',
run(_cx) {},
},
],
},
{
id: 'delete',
placement: 'more',
actions: [
{
id: 'delete',
label: 'Delete',
run(_cx) {},
},
],
},
],
} as const satisfies ToolbarModuleConfig;
21 changes: 15 additions & 6 deletions blocksuite/affine/block-image/src/image-spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
import {
BlockFlavourIdentifier,
BlockViewExtension,
CommandExtension,
FlavourExtension,
Expand All @@ -7,15 +9,18 @@ import {
import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';

import { ImageBlockAdapterExtensions } from './adapters/extension.js';
import { commands } from './commands/index.js';
import { ImageBlockService, ImageDropOption } from './image-service.js';
import { ImageBlockAdapterExtensions } from './adapters/extension';
import { commands } from './commands/index';
import { ImageBlockService, ImageDropOption } from './image-service';
import { BuiltinToolbarConfig } from './toolbar';

const Flavour = 'affine:image';

export const ImageBlockSpec: ExtensionType[] = [
FlavourExtension('affine:image'),
FlavourExtension(Flavour),
ImageBlockService,
CommandExtension(commands),
BlockViewExtension('affine:image', model => {
BlockViewExtension(Flavour, model => {
const parent = model.doc.getParent(model.id);

if (parent?.flavour === 'affine:surface') {
Expand All @@ -24,9 +29,13 @@ export const ImageBlockSpec: ExtensionType[] = [

return literal`affine-image`;
}),
WidgetViewMapExtension('affine:image', {
WidgetViewMapExtension(Flavour, {
imageToolbar: literal`affine-image-toolbar-widget`,
}),
ImageDropOption,
ImageBlockAdapterExtensions,
ToolbarModuleExtension({
id: BlockFlavourIdentifier(Flavour),
config: BuiltinToolbarConfig,
}),
].flat();
54 changes: 54 additions & 0 deletions blocksuite/affine/block-image/src/toolbar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';

export const BuiltinToolbarConfig = {
actions: [
{
id: 'download',
tooltip: 'Download',
run(_cx) {},
},
{
id: 'caption',
tooltip: 'Caption',
run(_cx) {},
},
{
id: 'clipboard',
placement: 'more',
actions: [
{
id: 'copy',
label: 'Copy',
run(_cx) {},
},
{
id: 'duplicate',
label: 'Duplicate',
run(_cx) {},
},
],
},
{
id: 'conversions',
placement: 'more',
actions: [
{
id: 'turn-into-card-view',
label: 'Turn into card view',
run(_cx) {},
},
],
},
{
id: 'delete',
placement: 'more',
actions: [
{
id: 'delete',
label: 'Delete',
run(_cx) {},
},
],
},
],
} as const satisfies ToolbarModuleConfig;
1 change: 1 addition & 0 deletions blocksuite/affine/shared/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from './parse-url-service';
export * from './quick-search-service';
export * from './telemetry-service';
export * from './theme-service';
export * from './toolbar-service';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './module';
export * from './registry';
// export type { ToolbarAction, ToolbarContext, ToolbarModuleConfig } from './types';
export * from './types';
export * from './utils';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { BlockFlavourIdentifier } from '@blocksuite/block-std';

import type { ToolbarModuleConfig } from './types';

export type ToolbarModule = {
readonly id: ReturnType<typeof BlockFlavourIdentifier>;

readonly config: ToolbarModuleConfig;
};
59 changes: 59 additions & 0 deletions blocksuite/affine/shared/src/services/toolbar-service/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
type BlockStdScope,
LifeCycleWatcher,
StdIdentifier,
} from '@blocksuite/block-std';
import {
type Container,
createIdentifier,
createScope,
} from '@blocksuite/global/di';
import type { ExtensionType } from '@blocksuite/store';

import type { ToolbarModule } from './module';

export const ToolbarModuleIdentifier = createIdentifier<ToolbarModule>(
'AffineToolbarModuleIdentifier'
);

export const ToolbarModulesIdentifier = createIdentifier<
Map<string, ToolbarModule>
>('AffineToolbarModulesIdentifier');

export const ToolbarRegistryScope = createScope('AffineToolbarRegistryScope');

export const ToolbarRegistryIdentifier =
createIdentifier<ToolbarRegistryExtension>('AffineToolbarRegistryIdentifier');

export function ToolbarModuleExtension(module: ToolbarModule): ExtensionType {
return {
setup: di => {
di.scope(ToolbarRegistryScope).addImpl(
ToolbarModuleIdentifier(module.id.variant),
module
);
},
};
}

export class ToolbarRegistryExtension extends LifeCycleWatcher {
constructor(
std: BlockStdScope,
readonly modules: Map<string, ToolbarModule>
) {
super(std);
}

static override readonly key = 'toolbar-registry';

static override setup(di: Container) {
di.scope(ToolbarRegistryScope)
.addImpl(ToolbarModulesIdentifier, provider =>
provider.getAll(ToolbarModuleIdentifier)
)
.addImpl(ToolbarRegistryIdentifier, this, [
StdIdentifier,
ToolbarModulesIdentifier,
]);
}
}
54 changes: 54 additions & 0 deletions blocksuite/affine/shared/src/services/toolbar-service/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { BlockStdScope } from '@blocksuite/block-std';
import type { TemplateResult } from 'lit';

type ActionBase = {
id: string;
score?: number;
when?: (cx: ToolbarContext) => boolean;
placement?: 'start' | 'end' | 'more';
};

export type ToolbarAction = ActionBase & {
label?: string;
icon?: TemplateResult;
tooltip?: string;
content?: (cx: ToolbarContext) => TemplateResult | null;
run: (cx: ToolbarContext) => void;
};

// Generates an action at runtime
export type ToolbarActionGenerator = ActionBase & {
generate: (cx: ToolbarContext) => ToolbarAction;
};

export type ToolbarActionGroup = ActionBase & {
actions: ToolbarAction[];
content?: (cx: ToolbarContext) => TemplateResult | null;
};

// Generates an action group at runtime
export type ToolbarActionGroupGenerator = ActionBase & {
generate: (cx: ToolbarContext) => ToolbarActionGroup;
};

export type ToolbarActions<
T extends ActionBase =
| ToolbarAction
| ToolbarActionGenerator
| ToolbarActionGroup
| ToolbarActionGroupGenerator,
> = T[];

abstract class ToolbarContextBase {
constructor(readonly std: BlockStdScope) {}

get isReadonly() {
return this.std.store.readonly;
}
}

export class ToolbarContext extends ToolbarContextBase {}

export interface ToolbarModuleConfig {
actions: ToolbarActions;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function generateActionIdWith(
flavour: string,
name: string,
prefix = 'com.affine.toolbar.internal'
) {
return `${prefix}.${flavour}.${name}`;
}
15 changes: 14 additions & 1 deletion blocksuite/affine/widget-toolbar/src/toolbar.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import {
ToolbarRegistryIdentifier,
ToolbarRegistryScope,
} from '@blocksuite/affine-shared/services';
import { WidgetComponent } from '@blocksuite/block-std';

export const AFFINE_TOOLBAR_WIDGET = 'affine-toolbar-widget';

export class AffineToolbarWidget extends WidgetComponent {}
export class AffineToolbarWidget extends WidgetComponent {
override connectedCallback() {
super.connectedCallback();

const toolbarRegistry = this.std.container
.provider(ToolbarRegistryScope, this.std.provider)
.get(ToolbarRegistryIdentifier);
console.log(toolbarRegistry);
}
}
Loading

0 comments on commit 69a85b7

Please sign in to comment.