Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): add toolbar registry extension #9572

Draft
wants to merge 1 commit into
base: fundon/01_07_add_affine_toolbar_widget
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
41 changes: 41 additions & 0 deletions blocksuite/affine/shared/src/services/toolbar-service/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { TemplateResult } from 'lit';

import type { ToolbarContext } from './context';

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[];
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ToolbarActions } from './action';

export type ToolbarModuleConfig = {
actions: ToolbarActions;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { BlockStdScope } from '@blocksuite/block-std';

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

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

export class ToolbarContext extends ToolbarContextBase {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './action';
export * from './config';
export * from './context';
export * from './module';
export * from './registry';
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 './config';

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

readonly config: ToolbarModuleConfig;
};
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,
]);
}
}
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
Loading