From 147fa9a6b13dfb051c6c7159b647462932151750 Mon Sep 17 00:00:00 2001 From: Flrande <1978616327@qq.com> Date: Thu, 15 May 2025 10:59:38 +0000 Subject: [PATCH] feat(editor): add line number display option for code block (#12305) ## Summary by CodeRabbit - **New Features** - Added a toggle in the code block toolbar to show or hide line numbers for individual code blocks. - The display of line numbers now respects both global and per-block settings, allowing more flexible control. - **Style** - Updated styles to hide line numbers when disabled via the new toggle option. - **Tests** - Added end-to-end tests to verify toggling line numbers visibility and undo/redo behavior. --- .../affine/blocks/code/src/code-block.ts | 25 ++++++------- .../blocks/code/src/code-toolbar/config.ts | 36 +++++++++++++++++++ blocksuite/affine/blocks/code/src/styles.ts | 4 +++ .../model/src/blocks/code/code-model.ts | 2 ++ tests/blocksuite/e2e/code/crud.spec.ts | 28 +++++++++++++++ tests/blocksuite/e2e/code/utils.ts | 6 ++++ 6 files changed, 89 insertions(+), 12 deletions(-) diff --git a/blocksuite/affine/blocks/code/src/code-block.ts b/blocksuite/affine/blocks/code/src/code-block.ts index 12fa4efa8f..36d0ff8a7f 100644 --- a/blocksuite/affine/blocks/code/src/code-block.ts +++ b/blocksuite/affine/blocks/code/src/code-block.ts @@ -388,8 +388,10 @@ export class CodeBlockComponent extends CaptionedBlockComponent override renderBlock(): TemplateResult<1> { const showLineNumbers = - this.std.getOptional(CodeBlockConfigExtension.identifier) - ?.showLineNumbers ?? true; + (this.std.getOptional(CodeBlockConfigExtension.identifier) + ?.showLineNumbers ?? + true) && + (this.model.props.lineNumber ?? true); const preview = !!this.model.props.preview; const previewContext = this.std.getOptional( @@ -403,6 +405,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent 'affine-code-block-container': true, mobile: IS_MOBILE, wrap: this.model.props.wrap, + 'disable-line-numbers': !showLineNumbers, })} > .enableUndoRedo=${false} .wrapText=${this.model.props.wrap} .verticalScrollContainerGetter=${() => getViewportElement(this.host)} - .vLineRenderer=${showLineNumbers - ? (vLine: VLine) => { - return html` - ${vLine.index + 1} - ${vLine.renderVElements()} - `; - } - : undefined} + .vLineRenderer=${(vLine: VLine) => { + return html` + ${vLine.index + 1} + ${vLine.renderVElements()} + `; + }} >
= { }; }, }, + { + type: 'line-number', + when: ({ std }) => + std.getOptional(CodeBlockConfigExtension.identifier)?.showLineNumbers ?? + true, + generate: ({ blockComponent, close }) => { + return { + action: () => {}, + render: () => { + const lineNumber = blockComponent.model.props.lineNumber ?? true; + const label = lineNumber ? 'Cancel line number' : 'Line number'; + return html` + { + blockComponent.store.updateBlock(blockComponent.model, { + lineNumber: !lineNumber, + }); + + close(); + }} + aria-label=${label} + > + ${NumberedListIcon()} + ${label} + + + `; + }, + }; + }, + }, { type: 'duplicate', label: 'Duplicate', diff --git a/blocksuite/affine/blocks/code/src/styles.ts b/blocksuite/affine/blocks/code/src/styles.ts index 4be715c106..85be45d648 100644 --- a/blocksuite/affine/blocks/code/src/styles.ts +++ b/blocksuite/affine/blocks/code/src/styles.ts @@ -50,6 +50,10 @@ export const codeBlockStyles = css` user-select: none; } + .affine-code-block-container.disable-line-numbers .line-number { + display: none; + } + affine-code .affine-code-block-preview { padding: 12px; } diff --git a/blocksuite/affine/model/src/blocks/code/code-model.ts b/blocksuite/affine/model/src/blocks/code/code-model.ts index 7c0e4cb3b7..abb7f24739 100644 --- a/blocksuite/affine/model/src/blocks/code/code-model.ts +++ b/blocksuite/affine/model/src/blocks/code/code-model.ts @@ -13,6 +13,7 @@ type CodeBlockProps = { wrap: boolean; caption: string; preview?: boolean; + lineNumber?: boolean; } & BlockMeta; export const CodeBlockSchema = defineBlockSchema({ @@ -24,6 +25,7 @@ export const CodeBlockSchema = defineBlockSchema({ wrap: false, caption: '', preview: undefined, + lineNumber: undefined, 'meta:createdAt': undefined, 'meta:createdBy': undefined, 'meta:updatedAt': undefined, diff --git a/tests/blocksuite/e2e/code/crud.spec.ts b/tests/blocksuite/e2e/code/crud.spec.ts index 95a633e8d8..203c75b10a 100644 --- a/tests/blocksuite/e2e/code/crud.spec.ts +++ b/tests/blocksuite/e2e/code/crud.spec.ts @@ -321,6 +321,34 @@ test('undo code block wrap can work', async ({ page }, testInfo) => { ); }); +test('toggle code block line number can work', 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(); + await (await codeBlockController.openMore()).cancelLineNumberButton.click(); + + await expect(lineNumber).toBeHidden(); + + await undoByKeyboard(page); + await expect(lineNumber).toBeVisible(); + + await redoByKeyboard(page); + await expect(lineNumber).toBeHidden(); + + await codeBlockController.codeBlock.hover(); + await (await codeBlockController.openMore()).lineNumberButton.click(); + + await expect(lineNumber).toBeVisible(); +}); + test('code block toolbar widget can appear and disappear during mousemove', async ({ page, }) => { diff --git a/tests/blocksuite/e2e/code/utils.ts b/tests/blocksuite/e2e/code/utils.ts index c9783c9c3c..54f8d4f096 100644 --- a/tests/blocksuite/e2e/code/utils.ts +++ b/tests/blocksuite/e2e/code/utils.ts @@ -34,6 +34,10 @@ export function getCodeBlock(page: Page) { const cancelWrapButton = menu.getByRole('button', { name: 'Cancel wrap' }); const duplicateButton = menu.getByRole('button', { name: 'Duplicate' }); const deleteButton = menu.getByRole('button', { name: 'Delete' }); + const lineNumberButton = menu.getByRole('button', { name: 'Line number' }); + const cancelLineNumberButton = menu.getByRole('button', { + name: 'Cancel line number', + }); return { menu, @@ -41,6 +45,8 @@ export function getCodeBlock(page: Page) { cancelWrapButton, duplicateButton, deleteButton, + lineNumberButton, + cancelLineNumberButton, }; };