diff --git a/blocksuite/affine/blocks/code/src/code-toolbar/components/lang-button.ts b/blocksuite/affine/blocks/code/src/code-toolbar/components/lang-button.ts index 481614395b..b4ef965e5f 100644 --- a/blocksuite/affine/blocks/code/src/code-toolbar/components/lang-button.ts +++ b/blocksuite/affine/blocks/code/src/code-toolbar/components/lang-button.ts @@ -18,6 +18,8 @@ import { html } from 'lit/static-html.js'; import type { CodeBlockComponent } from '../..'; +const PLAIN_TEXT_ID = '__plain_text__'; + export class LanguageListButton extends WithDisposable( SignalWatcher(LitElement) ) { @@ -75,7 +77,8 @@ export class LanguageListButton extends WithDisposable( sortedBundledLanguages.unshift(item); } this.blockComponent.store.transact(() => { - this.blockComponent.model.props.language$.value = item.name; + this.blockComponent.model.props.language$.value = + item.name === PLAIN_TEXT_ID ? null : item.name; }); const std = this.blockComponent.std; @@ -90,7 +93,12 @@ export class LanguageListButton extends WithDisposable( control: item.name, }); }, - active: item => item.name === this.blockComponent.model.props.language, + active: item => { + if (item.name === PLAIN_TEXT_ID) { + return this.blockComponent.model.props.language === null; + } + return item.name === this.blockComponent.model.props.language; + }, items: this._sortedBundledLanguages, }; @@ -111,15 +119,28 @@ export class LanguageListButton extends WithDisposable( override connectedCallback(): void { super.connectedCallback(); + const plainTextItem: FilterableListItem = { + label: 'Plain Text', + name: PLAIN_TEXT_ID, + aliases: ['plain', 'text', 'plaintext', 'txt', 'none'], + }; + const langList = localStorage.getItem('blocksuite:code-block:lang-list'); if (langList) { this._sortedBundledLanguages = JSON.parse(langList); + // Ensure Plain Text item is always in the list + if ( + !this._sortedBundledLanguages.some(item => item.name === PLAIN_TEXT_ID) + ) { + this._sortedBundledLanguages.unshift(plainTextItem); + } } else { - this._sortedBundledLanguages = this.blockComponent.langs.map(lang => ({ + const mappedLanguages = this.blockComponent.langs.map(lang => ({ label: lang.name, name: lang.id, aliases: lang.aliases, })); + this._sortedBundledLanguages = [plainTextItem, ...mappedLanguages]; } this.disposables.add(() => { diff --git a/tests/affine-local/e2e/blocksuite/code/crud.spec.ts b/tests/affine-local/e2e/blocksuite/code/crud.spec.ts index a1481ef90a..0bd0e40628 100644 --- a/tests/affine-local/e2e/blocksuite/code/crud.spec.ts +++ b/tests/affine-local/e2e/blocksuite/code/crud.spec.ts @@ -19,6 +19,26 @@ test.describe('Code Block Autocomplete Operations', () => { }); }); +test.describe('Code Block Language Selector', () => { + test('switch language and back to plain text', async ({ page }) => { + await initCodeBlockByOneStep(page); + const code = page.locator('affine-code'); + + await code.hover({ + position: { x: 155, y: 65 }, + }); + await page.getByTestId('lang-button').click(); + await page.getByRole('button', { name: 'Rust' }).click(); + + await expect(page.getByTestId('lang-button')).toHaveText('Rust'); + + await page.getByTestId('lang-button').click(); + await page.getByRole('button', { name: 'Plain Text' }).click(); + + await expect(page.getByTestId('lang-button')).toHaveText('Plain Text'); + }); +}); + test.describe('Code Block Preview', () => { test('enable html preview', async ({ page }) => { const code = page.locator('affine-code');