Skip to content

feat(markdown-renderer): add copy code button for code blocks#1812

Draft
Killusions wants to merge 3 commits intomainfrom
feat/markdown-renderer-copy-button
Draft

feat(markdown-renderer): add copy code button for code blocks#1812
Killusions wants to merge 3 commits intomainfrom
feat/markdown-renderer-copy-button

Conversation

@Killusions
Copy link
Copy Markdown
Member

@Killusions Killusions commented Apr 2, 2026

Merge after #1810 and #1811

Adds a button to copy code blocks to the rendered markdown (with a translatable label)


Documentation.
Examples.
Dashboards Demo.
Playwright report.

Coverage Reports:

Code Coverage

Add max-inline-size constraint to message bubble and wrapper to prevent
overflow. Fix attachment preview backdrop click behavior. Use index-based
tracking for message list and add scroll-to-bottom on message send.
Update example action labels for clarity.
…line and caching

Rewrite the markdown renderer to use a recursive processing pipeline with
placeholder-based architecture. Add SSR support via optional doc/isBrowser
parameters. Introduce LRU caching for code blocks and tables. Improve
table rendering with proper thead/th support. Add code-wrapper structure
with language labels. Update SCSS for new structural layout including
code-wrapper, table-wrapper, and table-scroll-container.
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the markdown renderer to support Server-Side Rendering (SSR), implements LRU caching for code blocks and tables, and adds a copy-to-clipboard feature for code snippets. It also updates chat message action labels to align with UX guidelines. Feedback highlights a duplicate code block with conflicting definitions, a caching issue where locale changes are not reflected in translated labels, and potential SSR errors or memory leaks caused by direct event listener attachments.

Comment on lines +35 to +47
import { getCachedOrCreateElement } from './markdown-renderer-helpers';

const CACHE_SIZE = 100;

export type MarkdownRendererOptions = Record<string, never>;

interface ProcessOptions {
allowCodeBlocks?: boolean;
allowBlockquotes?: boolean;
allowTables?: boolean;
allowInlineCode?: boolean;
allowLinks?: boolean;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This block of code is a duplicate of lines 10-33 and contains conflicting definitions. Specifically, MarkdownRendererOptions is redefined as Record<string, never>, which will break the component implementation that expects the interface defined earlier. Please remove this entire duplicate block.

* Create cache key for code block
*/
const createCodeBlockCacheKey = (language: string, content: string): string => {
return `${language}|||${content}|||${options?.copyCodeButton ?? ''}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The cache key for code blocks should include the current language or a locale identifier. Since the copyCodeButton label is translated inside the createHtml callback (which only runs on a cache miss), switching the application language will not update the labels for code blocks already present in the cache. Including a locale key in the cache key ensures that translations are correctly updated when the language changes.

Comment on lines +637 to +653
if (isInBrowser) {
div.querySelectorAll('.copy-code-btn').forEach(btn => {
btn.addEventListener('click', e => {
const button = e.target as HTMLButtonElement;
const codeId = button.getAttribute('data-code-id');
if (!codeId) return;

const codeElement = div.querySelector(`#${codeId}`);
if (!codeElement) return;

const code = codeElement.textContent ?? '';
navigator.clipboard.writeText(code).catch(() => {
console.warn('Failed to copy code to clipboard');
});
});
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Adding event listeners directly to DOM elements inside the renderer can lead to memory leaks and will cause errors during Server-Side Rendering (SSR) as addEventListener is not available on the server. It is safer to use event delegation on the container div and ensure these browser-specific operations are wrapped in an isPlatformBrowser check. Given this is a standalone renderer function, consider adding a single listener to the root div that handles clicks for all copy buttons via data attributes.

References
  1. When using browser-dependent UI features, ensure they are only executed in a browser environment. Use a check like isPlatformBrowser to prevent errors during Server-Side Rendering (SSR).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant