feat(callout): add formatbar with background color selection

- Add background property to CalloutBlockModel with default white color
- Implement dynamic background color rendering in CalloutBlockComponent
- Create toolbar configuration with color palette for background selection
- Register toolbar extension in CalloutViewExtension
- Support all note background colors with visual feedback for current selection
- Maintain consistency with other block formatbar implementations
This commit is contained in:
zzj3720
2025-09-24 23:56:57 +08:00
parent e3d88ab3f2
commit 5e8691367d
4 changed files with 89 additions and 3 deletions
@@ -1,14 +1,13 @@
import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
import { createLitPortal } from '@blocksuite/affine-components/portal';
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
import { type CalloutBlockModel } from '@blocksuite/affine-model';
import { type CalloutBlockModel, DefaultTheme } from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
import {
DocModeProvider,
ThemeProvider,
} from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import type { BlockComponent } from '@blocksuite/std';
import { flip, offset } from '@floating-ui/dom';
import { css, html } from 'lit';
@@ -26,7 +25,6 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
align-items: flex-start;
padding: 5px 10px;
border-radius: 8px;
background-color: ${unsafeCSSVarV2('block/callout/background/grey')};
}
.affine-callout-emoji-container {
@@ -139,10 +137,23 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
override renderBlock() {
const emoji = this.model.props.emoji$.value;
const background = this.model.props.background$.value;
const themeProvider = this.std.get(ThemeProvider);
const theme = themeProvider.theme$.value;
const backgroundColor = themeProvider.generateColorProperty(
background,
DefaultTheme.NoteBackgroundColorMap.White,
theme
);
return html`
<div
class="affine-callout-block-container"
@click=${this._handleBlockClick}
style=${styleMap({
backgroundColor: backgroundColor,
})}
>
<div
@click=${this._toggleEmojiMenu}
@@ -0,0 +1,69 @@
import { CalloutBlockModel, DefaultTheme } from '@blocksuite/affine-model';
import {
type ToolbarAction,
type ToolbarActionGroup,
type ToolbarModuleConfig,
ToolbarModuleExtension,
} from '@blocksuite/affine-shared/services';
import { PaletteIcon } from '@blocksuite/icons/lit';
import { BlockFlavourIdentifier } from '@blocksuite/std';
import type { ExtensionType } from '@blocksuite/store';
import { html } from 'lit';
const backgroundColorAction = {
id: 'background-color',
label: 'Background Color',
tooltip: 'Change background color',
icon: PaletteIcon(),
run() {
// This will be handled by the content function
},
content(ctx) {
const model = ctx.getCurrentModelByType(CalloutBlockModel);
if (!model) return null;
const currentBackground = model.props.background;
const colors = DefaultTheme.NoteBackgroundColorPalettes;
return html`
<div
style="display: flex; flex-wrap: wrap; gap: 4px; padding: 8px; max-width: 200px;"
>
${colors.map(
color => html`
<button
style="width: 24px; height: 24px; border-radius: 4px; border: 2px solid ${currentBackground ===
color.value
? 'var(--affine-primary-color)'
: 'transparent'}; background-color: ${color.value}; cursor: pointer; padding: 0; margin: 0;"
@click=${() => {
ctx.store.updateBlock(model, { background: color.value });
}}
title=${color.key}
></button>
`
)}
</div>
`;
},
} satisfies ToolbarAction;
const builtinToolbarConfig = {
actions: [
{
id: 'style',
actions: [backgroundColorAction],
} satisfies ToolbarActionGroup<ToolbarAction>,
],
} as const satisfies ToolbarModuleConfig;
export const createBuiltinToolbarConfigExtension = (
flavour: string
): ExtensionType[] => {
return [
ToolbarModuleExtension({
id: BlockFlavourIdentifier(flavour),
config: builtinToolbarConfig,
}),
];
};
@@ -8,6 +8,7 @@ import { literal } from 'lit/static-html.js';
import { CalloutKeymapExtension } from './callout-keymap';
import { calloutSlashMenuConfig } from './configs/slash-menu';
import { createBuiltinToolbarConfigExtension } from './configs/toolbar';
import { effects } from './effects';
export class CalloutViewExtension extends ViewExtensionProvider {
@@ -25,6 +26,7 @@ export class CalloutViewExtension extends ViewExtensionProvider {
BlockViewExtension('affine:callout', literal`affine-callout`),
CalloutKeymapExtension,
SlashMenuConfigExtension('affine:callout', calloutSlashMenuConfig),
...createBuiltinToolbarConfigExtension('affine:callout'),
]);
}
}
@@ -5,11 +5,14 @@ import {
type Text,
} from '@blocksuite/store';
import type { Color } from '../../themes/index.js';
import { DefaultTheme } from '../../themes/index.js';
import type { BlockMeta } from '../../utils/types';
export type CalloutProps = {
emoji: string;
text: Text;
background: Color;
} & BlockMeta;
export const CalloutBlockSchema = defineBlockSchema({
@@ -17,6 +20,7 @@ export const CalloutBlockSchema = defineBlockSchema({
props: (internal): CalloutProps => ({
emoji: '😀',
text: internal.Text(),
background: DefaultTheme.NoteBackgroundColorMap.White,
'meta:createdAt': undefined,
'meta:updatedAt': undefined,
'meta:createdBy': undefined,