diff --git a/blocksuite/affine/blocks/code/src/code-toolbar/components/code-toolbar.ts b/blocksuite/affine/blocks/code/src/code-toolbar/components/code-toolbar.ts
index 4ee4c0869..bfd92ce08 100644
--- a/blocksuite/affine/blocks/code/src/code-toolbar/components/code-toolbar.ts
+++ b/blocksuite/affine/blocks/code/src/code-toolbar/components/code-toolbar.ts
@@ -11,7 +11,6 @@ import { MoreVerticalIcon } from '@blocksuite/icons/lit';
import { flip, offset } from '@floating-ui/dom';
import { css, html, LitElement } from 'lit';
import { property, query, state } from 'lit/decorators.js';
-import { styleMap } from 'lit/directives/style-map.js';
import type { CodeBlockToolbarContext } from '../context.js';
@@ -82,18 +81,10 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
createLitPortal({
template: html`
-
+
`,
// should be greater than block-selection z-index as selection and popover wil share the same stacking context(editor-host)
portalStyles: {
diff --git a/blocksuite/affine/blocks/code/src/code-toolbar/components/more-menu.ts b/blocksuite/affine/blocks/code/src/code-toolbar/components/more-menu.ts
new file mode 100644
index 000000000..a5587ae02
--- /dev/null
+++ b/blocksuite/affine/blocks/code/src/code-toolbar/components/more-menu.ts
@@ -0,0 +1,52 @@
+import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
+import { renderGroups } from '@blocksuite/affine-components/toolbar';
+import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
+import { ShadowlessElement } from '@blocksuite/std';
+import { html } from 'lit';
+import { property } from 'lit/decorators.js';
+import { styleMap } from 'lit/directives/style-map.js';
+
+import type { CodeBlockToolbarContext } from '../context.js';
+
+export class AffineCodeMoreMenu extends SignalWatcher(
+ WithDisposable(ShadowlessElement)
+) {
+ override firstUpdated() {
+ this.disposables.add(
+ this.context.blockComponent.model.propsUpdated.subscribe(({ key }) => {
+ if (key === 'wrap' || key === 'lineNumber') {
+ this.requestUpdate();
+ }
+ })
+ );
+ }
+
+ override render() {
+ return html`
+
+ `;
+ }
+
+ @property({ attribute: false })
+ accessor context!: CodeBlockToolbarContext;
+
+ @property({ attribute: false })
+ accessor moreGroups!: MenuItemGroup[];
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'affine-code-more-menu': AffineCodeMoreMenu;
+ }
+}
diff --git a/blocksuite/affine/blocks/code/src/code-toolbar/config.ts b/blocksuite/affine/blocks/code/src/code-toolbar/config.ts
index b6e4cfa26..e6bcdd1a2 100644
--- a/blocksuite/affine/blocks/code/src/code-toolbar/config.ts
+++ b/blocksuite/affine/blocks/code/src/code-toolbar/config.ts
@@ -174,7 +174,8 @@ export const toggleGroup: MenuItemGroup = {
return html`
{
- blockComponent.setWrap(!wrapped);
+ const currentWrap = blockComponent.model.props.wrap;
+ blockComponent.setWrap(!currentWrap);
}}
aria-label=${label}
>
@@ -204,8 +205,10 @@ export const toggleGroup: MenuItemGroup = {
return html`
{
+ const currentLineNumber =
+ blockComponent.model.props.lineNumber ?? true;
blockComponent.store.updateBlock(blockComponent.model, {
- lineNumber: !lineNumber,
+ lineNumber: !currentLineNumber,
});
}}
aria-label=${label}
diff --git a/blocksuite/affine/blocks/code/src/effects.ts b/blocksuite/affine/blocks/code/src/effects.ts
index 403131c34..31af358c7 100644
--- a/blocksuite/affine/blocks/code/src/effects.ts
+++ b/blocksuite/affine/blocks/code/src/effects.ts
@@ -5,12 +5,14 @@ import {
} from './code-toolbar';
import { AffineCodeToolbar } from './code-toolbar/components/code-toolbar';
import { LanguageListButton } from './code-toolbar/components/lang-button';
+import { AffineCodeMoreMenu } from './code-toolbar/components/more-menu';
import { PreviewButton } from './code-toolbar/components/preview-button';
import { AffineCodeUnit } from './highlight/affine-code-unit';
export function effects() {
customElements.define('language-list-button', LanguageListButton);
customElements.define('affine-code-toolbar', AffineCodeToolbar);
+ customElements.define('affine-code-more-menu', AffineCodeMoreMenu);
customElements.define(AFFINE_CODE_TOOLBAR_WIDGET, AffineCodeToolbarWidget);
customElements.define('affine-code-unit', AffineCodeUnit);
customElements.define('affine-code', CodeBlockComponent);
@@ -21,6 +23,7 @@ declare global {
interface HTMLElementTagNameMap {
'language-list-button': LanguageListButton;
'affine-code-toolbar': AffineCodeToolbar;
+ 'affine-code-more-menu': AffineCodeMoreMenu;
'preview-button': PreviewButton;
[AFFINE_CODE_TOOLBAR_WIDGET]: AffineCodeToolbarWidget;
}
diff --git a/blocksuite/affine/components/src/toolbar/menu-button.ts b/blocksuite/affine/components/src/toolbar/menu-button.ts
index 314ea2250..2ccdbc3b3 100644
--- a/blocksuite/affine/components/src/toolbar/menu-button.ts
+++ b/blocksuite/affine/components/src/toolbar/menu-button.ts
@@ -187,6 +187,7 @@ export class EditorMenuAction extends LitElement {
color: var(--affine-text-primary-color);
font-weight: 400;
min-height: 30px; // 22 + 8
+ user-select: none;
}
:host(:hover),
diff --git a/tests/blocksuite/e2e/code/crud.spec.ts b/tests/blocksuite/e2e/code/crud.spec.ts
index a73ee2e82..e0b24a25f 100644
--- a/tests/blocksuite/e2e/code/crud.spec.ts
+++ b/tests/blocksuite/e2e/code/crud.spec.ts
@@ -286,6 +286,32 @@ test('toggle code block wrap can work', async ({ page }, testInfo) => {
);
});
+test('toggle code block wrap can work in the same more menu', async ({
+ page,
+}) => {
+ await enterPlaygroundRoom(page);
+ await initEmptyCodeBlockState(page);
+
+ const codeBlockController = getCodeBlock(page);
+ const codeBlockContainer = page.locator(
+ 'affine-code .affine-code-block-container'
+ );
+
+ await codeBlockController.codeBlock.hover();
+ const moreMenu = await codeBlockController.openMore();
+
+ await moreMenu.wrapButton.click();
+
+ await expect(moreMenu.menu).toBeVisible();
+ await expect(codeBlockContainer).toHaveClass(/wrap/);
+ await expect(moreMenu.cancelWrapButton).toBeVisible();
+
+ await moreMenu.cancelWrapButton.click();
+
+ await expect(moreMenu.menu).toBeVisible();
+ await expect(codeBlockContainer).not.toHaveClass(/wrap/);
+});
+
test('add caption works', async ({ page }, testInfo) => {
await enterPlaygroundRoom(page);
await initEmptyCodeBlockState(page);
@@ -355,6 +381,34 @@ test('toggle code block line number can work', async ({ page }) => {
await expect(lineNumber).toBeVisible();
});
+test('toggle code block line number can work in the same more menu', async ({
+ page,
+}) => {
+ await enterPlaygroundRoom(page);
+ await initEmptyCodeBlockState(page);
+ await focusRichText(page);
+
+ const lineNumber = page.locator('affine-code .line-number');
+
+ await expect(lineNumber).toBeVisible();
+
+ const codeBlockController = getCodeBlock(page);
+
+ await codeBlockController.codeBlock.hover();
+ const moreMenu = await codeBlockController.openMore();
+
+ await moreMenu.cancelLineNumberButton.click();
+
+ await expect(moreMenu.menu).toBeVisible();
+ await expect(lineNumber).toBeHidden();
+ await expect(moreMenu.lineNumberButton).toBeVisible();
+
+ await moreMenu.lineNumberButton.click();
+
+ await expect(moreMenu.menu).toBeVisible();
+ await expect(lineNumber).toBeVisible();
+});
+
test('code block toolbar widget can appear and disappear during mousemove', async ({
page,
}) => {