From 19ed4226f00c334a9226d9c7f470c291eb7745bf Mon Sep 17 00:00:00 2001 From: Jessy Latmi <69401292+JyVers@users.noreply.github.com> Date: Sat, 19 Sep 2026 11:28:04 +0200 Subject: [PATCH] fix(editor): add Plain Text option to language selector dropdown (#15492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem:** When a language was selected in a code block's language dropdown, there was no way to reset it back to "Plain Text" (no syntax highlighting). The dropdown only contained shiki languages, and the null value representing Plain Text had no corresponding selectable option. **Solution:** Added a "Plain Text" entry as the first item in the language selector dropdown. This entry: - Maps to language: null when selected - Shows a checkmark when Plain Text is active - Persists in localStorage alongside recently used languages - Is searchable via aliases ("plain", "text", "none") ## Summary by CodeRabbit * **New Features** * Added Plain Text as a selectable code block language. * Plain Text is available in newly generated and previously saved language lists. * Code blocks correctly show Plain Text when no language is selected. * Recognizes common names including “plain,” “text,” “plaintext,” “txt,” and “none.” * **Tests** * Added coverage for switching between Rust and Plain Text. --- .../code-toolbar/components/lang-button.ts | 27 ++++++++++++++++--- .../e2e/blocksuite/code/crud.spec.ts | 20 ++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) 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');