feat(editor): release callout (#13896)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Callout is no longer gated as an experimental option — it now
consistently appears in the editor’s slash menu and toolbar where
applicable.

* **Tests**
* End-to-end slash-menu tests updated to expect the Callout item in
search results and adjusted item ordering.

* **Chores**
  * Repository ignore rules updated to exclude .kiro files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
3720
2025-11-12 14:46:18 +08:00
committed by GitHub
parent 7e6ead4232
commit 6ec1948f62
6 changed files with 25 additions and 33 deletions

3
.gitignore vendored
View File

@@ -33,6 +33,9 @@ node_modules
!.vscode/launch.template.json !.vscode/launch.template.json
!.vscode/extensions.json !.vscode/extensions.json
# Kiro
.kiro
# misc # misc
/.sass-cache /.sass-cache
/connect.lock /connect.lock

View File

@@ -1,5 +1,4 @@
import { focusBlockEnd } from '@blocksuite/affine-shared/commands'; import { focusBlockEnd } from '@blocksuite/affine-shared/commands';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils'; import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu'; import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu';
import { FontIcon } from '@blocksuite/icons/lit'; import { FontIcon } from '@blocksuite/icons/lit';
@@ -18,10 +17,11 @@ export const calloutSlashMenuConfig: SlashMenuConfig = {
}, },
searchAlias: ['callout'], searchAlias: ['callout'],
group: '0_Basic@9', group: '0_Basic@9',
when: ({ std, model }) => { when: ({ model }) => {
return ( return !isInsideBlockByFlavour(
std.get(FeatureFlagService).getFlag('enable_callout') && model.store,
!isInsideBlockByFlavour(model.store, model, 'affine:edgeless-text') model,
'affine:edgeless-text'
); );
}, },
action: ({ model, std }) => { action: ({ model, std }) => {

View File

@@ -17,7 +17,6 @@ export interface BlockSuiteFlags {
enable_mobile_linked_doc_menu: boolean; enable_mobile_linked_doc_menu: boolean;
enable_mobile_database_editing: boolean; enable_mobile_database_editing: boolean;
enable_block_meta: boolean; enable_block_meta: boolean;
enable_callout: boolean;
enable_edgeless_scribbled_style: boolean; enable_edgeless_scribbled_style: boolean;
enable_table_virtual_scroll: boolean; enable_table_virtual_scroll: boolean;
enable_turbo_renderer: boolean; enable_turbo_renderer: boolean;
@@ -43,7 +42,6 @@ export class FeatureFlagService extends StoreExtension {
enable_mobile_linked_doc_menu: false, enable_mobile_linked_doc_menu: false,
enable_block_meta: true, enable_block_meta: true,
enable_mobile_database_editing: false, enable_mobile_database_editing: false,
enable_callout: false,
enable_edgeless_scribbled_style: false, enable_edgeless_scribbled_style: false,
enable_table_virtual_scroll: false, enable_table_virtual_scroll: false,
enable_turbo_renderer: false, enable_turbo_renderer: false,

View File

@@ -47,10 +47,7 @@ import {
getTextSelectionCommand, getTextSelectionCommand,
} from '@blocksuite/affine-shared/commands'; } from '@blocksuite/affine-shared/commands';
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts'; import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import { import { TelemetryProvider } from '@blocksuite/affine-shared/services';
FeatureFlagService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import type { AffineTextStyleAttributes } from '@blocksuite/affine-shared/types'; import type { AffineTextStyleAttributes } from '@blocksuite/affine-shared/types';
import { import {
createDefaultDoc, createDefaultDoc,
@@ -290,10 +287,11 @@ const textToolActionItems: KeyboardToolbarActionItem[] = [
{ {
name: 'Callout', name: 'Callout',
icon: FontIcon(), icon: FontIcon(),
showWhen: ({ std, rootComponent: { model } }) => { showWhen: ({ rootComponent: { model } }) => {
return ( return !isInsideBlockByFlavour(
std.get(FeatureFlagService).getFlag('enable_callout') && model.store,
!isInsideBlockByFlavour(model.store, model, 'affine:edgeless-text') model,
'affine:edgeless-text'
); );
}, },
action: ({ rootComponent: { model }, std }) => { action: ({ rootComponent: { model }, std }) => {

View File

@@ -95,16 +95,6 @@ export const AFFINE_FLAGS = {
configurable: isCanaryBuild, configurable: isCanaryBuild,
defaultState: true, defaultState: true,
}, },
enable_callout: {
category: 'blocksuite',
bsFlag: 'enable_callout',
displayName:
'com.affine.settings.workspace.experimental-features.enable-callout.name',
description:
'com.affine.settings.workspace.experimental-features.enable-callout.description',
configurable: isCanaryBuild,
defaultState: isCanaryBuild,
},
enable_emoji_folder_icon: { enable_emoji_folder_icon: {
category: 'affine', category: 'affine',

View File

@@ -577,9 +577,10 @@ test.describe('slash search', () => {
// search should active the first item // search should active the first item
await type(page, 'co'); await type(page, 'co');
await expect(slashItems).toHaveCount(3); await expect(slashItems).toHaveCount(4);
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']);
await expect(slashItems.nth(2).locator('.text')).toHaveText(['Callout']);
await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true');
await type(page, 'p'); await type(page, 'p');
@@ -588,9 +589,10 @@ test.describe('slash search', () => {
// assert backspace works // assert backspace works
await pressBackspace(page); await pressBackspace(page);
await expect(slashItems).toHaveCount(3); await expect(slashItems).toHaveCount(4);
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']);
await expect(slashItems.nth(2).locator('.text')).toHaveText(['Callout']);
await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true');
}); });
@@ -606,14 +608,15 @@ test.describe('slash search', () => {
await expect(slashMenu).toBeVisible(); await expect(slashMenu).toBeVisible();
await type(page, 'c'); await type(page, 'c');
await expect(slashItems).toHaveCount(9); await expect(slashItems).toHaveCount(10);
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Italic']); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Italic']);
await expect(slashItems.nth(2).locator('.text')).toHaveText(['New Doc']); await expect(slashItems.nth(2).locator('.text')).toHaveText(['Callout']);
await expect(slashItems.nth(3).locator('.text')).toHaveText(['Duplicate']); await expect(slashItems.nth(3).locator('.text')).toHaveText(['New Doc']);
await expect(slashItems.nth(4).locator('.text')).toHaveText(['Code Block']); await expect(slashItems.nth(4).locator('.text')).toHaveText(['Duplicate']);
await expect(slashItems.nth(5).locator('.text')).toHaveText(['Linked Doc']); await expect(slashItems.nth(5).locator('.text')).toHaveText(['Code Block']);
await expect(slashItems.nth(6).locator('.text')).toHaveText(['Attachment']); await expect(slashItems.nth(6).locator('.text')).toHaveText(['Linked Doc']);
await expect(slashItems.nth(7).locator('.text')).toHaveText(['Attachment']);
await type(page, 'b'); await type(page, 'b');
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Code Block']); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Code Block']);
}); });