fix(editor): reactive heading icon (#9729)

Close [BS-2407](https://linear.app/affine-design/issue/BS-2407/[bug]-edgeless-里选中-title-后更改,hint-没有改变)
This commit is contained in:
L-Sun
2025-01-16 08:32:15 +00:00
parent 509cdfd7bf
commit 969ac30874
5 changed files with 66 additions and 10 deletions
@@ -8,7 +8,7 @@ import {
} from '@blocksuite/affine-components/icons'; } from '@blocksuite/affine-components/icons';
import type { ParagraphBlockModel } from '@blocksuite/affine-model'; import type { ParagraphBlockModel } from '@blocksuite/affine-model';
import { ShadowlessElement } from '@blocksuite/block-std'; import { ShadowlessElement } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
import { cssVarV2 } from '@toeverything/theme/v2'; import { cssVarV2 } from '@toeverything/theme/v2';
import { css, html, nothing, unsafeCSS } from 'lit'; import { css, html, nothing, unsafeCSS } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
@@ -32,7 +32,9 @@ function HeadingIcon(i: number) {
} }
} }
export class ParagraphHeadingIcon extends WithDisposable(ShadowlessElement) { export class ParagraphHeadingIcon extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css` static override styles = css`
affine-paragraph-heading-icon .heading-icon { affine-paragraph-heading-icon .heading-icon {
display: flex; display: flex;
@@ -65,12 +67,14 @@ export class ParagraphHeadingIcon extends WithDisposable(ShadowlessElement) {
`; `;
override render() { override render() {
const type = this.model.type; const type = this.model.type$.value;
if (!type.startsWith('h')) return nothing; if (!type.startsWith('h')) return nothing;
const i = parseInt(type.slice(1)); const i = parseInt(type.slice(1));
return html`<div class="heading-icon">${HeadingIcon(i)}</div>`; return html`<div class="heading-icon" data-testid="heading-icon-${i}">
${HeadingIcon(i)}
</div>`;
} }
@property({ attribute: false }) @property({ attribute: false })
@@ -157,7 +157,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<
this._readonlyCollapsed = collapsed; this._readonlyCollapsed = collapsed;
// reset text selection when selected block is collapsed // reset text selection when selected block is collapsed
if (this.model.type.startsWith('h') && collapsed) { if (this.model.type$.value.startsWith('h') && collapsed) {
const collapsedSiblings = this.collapsedSiblings; const collapsedSiblings = this.collapsedSiblings;
const textSelection = this.host.selection.find(TextSelection); const textSelection = this.host.selection.find(TextSelection);
@@ -177,7 +177,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<
// # 456 // # 456
// //
// we need to update collapsed state of 123 when 456 converted to text // we need to update collapsed state of 123 when 456 converted to text
let beforeType = this.model.type; let beforeType = this.model.type$.peek();
this.disposables.add( this.disposables.add(
effect(() => { effect(() => {
const type = this.model.type$.value; const type = this.model.type$.value;
@@ -211,7 +211,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<
const collapsedSiblings = this.collapsedSiblings; const collapsedSiblings = this.collapsedSiblings;
let style = html``; let style = html``;
if (this.model.type.startsWith('h') && collapsed) { if (this.model.type$.value.startsWith('h') && collapsed) {
style = html` style = html`
<style> <style>
${collapsedSiblings.map(sibling => ${collapsedSiblings.map(sibling =>
@@ -245,7 +245,8 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<
[TOGGLE_BUTTON_PARENT_CLASS]: true, [TOGGLE_BUTTON_PARENT_CLASS]: true,
})} })}
> >
${this.model.type.startsWith('h') && collapsedSiblings.length > 0 ${this.model.type$.value.startsWith('h') &&
collapsedSiblings.length > 0
? html` ? html`
<affine-paragraph-heading-icon <affine-paragraph-heading-icon
.model=${this.model} .model=${this.model}
@@ -0,0 +1,42 @@
import { test } from '@affine-test/kit/playwright';
import { locateFormatBar } from '@affine-test/kit/utils/editor';
import { selectAllByKeyboard } from '@affine-test/kit/utils/keyboard';
import { openHomePage } from '@affine-test/kit/utils/load-page';
import {
clickNewPageButton,
waitForEditorLoad,
} from '@affine-test/kit/utils/page-logic';
import { expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await openHomePage(page);
await clickNewPageButton(page, 'Paragraph Test');
await waitForEditorLoad(page);
});
test('heading icon should be updated after change heading level', async ({
page,
}) => {
await page.keyboard.press('Enter');
await page.keyboard.type('Hello');
// Hello|
// empty paragraph
const paragraph = page.locator('affine-note affine-paragraph').nth(0);
await selectAllByKeyboard(page);
const formatBar = locateFormatBar(page);
await formatBar.locator('.paragraph-button').hover();
await formatBar.getByTestId('affine:paragraph/h1').click();
await paragraph.hover();
await expect(page.getByTestId('heading-icon-1')).toBeVisible();
await selectAllByKeyboard(page);
await formatBar.locator('.paragraph-button').hover();
await formatBar.getByTestId('affine:paragraph/h2').click();
await paragraph.hover();
await expect(page.getByTestId('heading-icon-1')).toBeHidden();
await expect(page.getByTestId('heading-icon-2')).toBeVisible();
});
@@ -1,4 +1,5 @@
import { test } from '@affine-test/kit/playwright'; import { test } from '@affine-test/kit/playwright';
import { locateFormatBar } from '@affine-test/kit/utils/editor';
import { openHomePage } from '@affine-test/kit/utils/load-page'; import { openHomePage } from '@affine-test/kit/utils/load-page';
import { import {
clickNewPageButton, clickNewPageButton,
@@ -21,7 +22,7 @@ test.describe('Format bar', () => {
await page.keyboard.press('Shift+ArrowLeft'); await page.keyboard.press('Shift+ArrowLeft');
await page.keyboard.press('Shift+ArrowLeft'); await page.keyboard.press('Shift+ArrowLeft');
const formatBar = page.locator('.affine-format-bar-widget'); const formatBar = locateFormatBar(page);
await formatBar.locator('.highlight-icon').hover(); await formatBar.locator('.highlight-icon').hover();
const fgGreenButton = formatBar.locator( const fgGreenButton = formatBar.locator(
'[data-testid="var(--affine-text-highlight-foreground-green)"]' '[data-testid="var(--affine-text-highlight-foreground-green)"]'
@@ -49,7 +50,7 @@ test.describe('Format bar', () => {
await page.keyboard.press('Shift+ArrowLeft'); await page.keyboard.press('Shift+ArrowLeft');
await page.keyboard.press('Shift+ArrowLeft'); await page.keyboard.press('Shift+ArrowLeft');
const formatBar = page.locator('.affine-format-bar-widget'); const formatBar = locateFormatBar(page);
await formatBar.locator('.highlight-icon').hover(); await formatBar.locator('.highlight-icon').hover();
const fgGreenButton = formatBar.locator( const fgGreenButton = formatBar.locator(
+8
View File
@@ -1,5 +1,6 @@
import type { AffineEditorContainer } from '@blocksuite/affine/presets'; import type { AffineEditorContainer } from '@blocksuite/affine/presets';
import { import {
AFFINE_FORMAT_BAR_WIDGET,
EDGELESS_ELEMENT_TOOLBAR_WIDGET, EDGELESS_ELEMENT_TOOLBAR_WIDGET,
EDGELESS_TOOLBAR_WIDGET, EDGELESS_TOOLBAR_WIDGET,
} from '@blocksuite/blocks'; } from '@blocksuite/blocks';
@@ -52,6 +53,13 @@ export function locateEditorContainer(page: Page, editorIndex = 0) {
return page.locator('[data-affine-editor-container]').nth(editorIndex); return page.locator('[data-affine-editor-container]').nth(editorIndex);
} }
// ================== Page ==================
export function locateFormatBar(page: Page, editorIndex = 0) {
return locateEditorContainer(page, editorIndex).locator(
AFFINE_FORMAT_BAR_WIDGET
);
}
// ================== Edgeless ================== // ================== Edgeless ==================
export async function getEdgelessSelectedIds(page: Page, editorIndex = 0) { export async function getEdgelessSelectedIds(page: Page, editorIndex = 0) {