mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-05-08 22:07:32 +08:00
fix(editor): make repeated "Cancel line number" clicks work in code blocks (#14804)
Fixes #13555 https://github.com/user-attachments/assets/12e55c21-080c-4c69-9780-893ccad25b45 ## Summary - make the code block More popup reactive to `wrap` and `lineNumber` prop updates - read the latest toggle state at click time so same-menu double toggles do not reuse stale closures - add e2e coverage for wrap and line number toggling twice without closing the More menu ## Bug Reason - the code block More popup was rendered as a static portal, so it stayed open without re-rendering after the first toggle - the `Cancel line number` and `Wrap` menu actions captured render-time state in their click handlers - after the first click updated the model, a second click in the same open menu reused stale state and wrote the same value again, so nothing changed visually ## Testing - yarn workspace @affine-test/blocksuite test e2e/code/crud.spec.ts <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Menu toggles now read and update the current wrap and line-number states reliably. * **Refactor** * Replaced inline popup rendering with a dedicated more-menu component for the code toolbar. * **Style** * Prevented text selection on menu action elements for smoother interaction. * **Tests** * Added e2e tests for wrap and line-number toggle flows. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -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`
|
||||
<editor-menu-content
|
||||
data-show
|
||||
class="more-popup-menu"
|
||||
style=${styleMap({
|
||||
'--content-padding': '8px',
|
||||
'--packed-height': '4px',
|
||||
})}
|
||||
>
|
||||
<div data-size="large" data-orientation="vertical">
|
||||
${renderGroups(this.moreGroups, this.context)}
|
||||
</div>
|
||||
</editor-menu-content>
|
||||
<affine-code-more-menu
|
||||
.context=${this.context}
|
||||
.moreGroups=${this.moreGroups}
|
||||
></affine-code-more-menu>
|
||||
`,
|
||||
// should be greater than block-selection z-index as selection and popover wil share the same stacking context(editor-host)
|
||||
portalStyles: {
|
||||
|
||||
@@ -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`
|
||||
<editor-menu-content
|
||||
data-show
|
||||
class="more-popup-menu"
|
||||
style=${styleMap({
|
||||
'--content-padding': '8px',
|
||||
'--packed-height': '4px',
|
||||
})}
|
||||
>
|
||||
<div data-size="large" data-orientation="vertical">
|
||||
${renderGroups(this.moreGroups, this.context)}
|
||||
</div>
|
||||
</editor-menu-content>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor context!: CodeBlockToolbarContext;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor moreGroups!: MenuItemGroup<CodeBlockToolbarContext>[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-code-more-menu': AffineCodeMoreMenu;
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,8 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
blockComponent.setWrap(!wrapped);
|
||||
const currentWrap = blockComponent.model.props.wrap;
|
||||
blockComponent.setWrap(!currentWrap);
|
||||
}}
|
||||
aria-label=${label}
|
||||
>
|
||||
@@ -204,8 +205,10 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
const currentLineNumber =
|
||||
blockComponent.model.props.lineNumber ?? true;
|
||||
blockComponent.store.updateBlock(blockComponent.model, {
|
||||
lineNumber: !lineNumber,
|
||||
lineNumber: !currentLineNumber,
|
||||
});
|
||||
}}
|
||||
aria-label=${label}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user