From 3e5781924eefed399a5d8fa8b029149dce14014b Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Sun, 2 Mar 2025 18:18:46 -0800 Subject: [PATCH] chore: improve CodeBlock performance with debouncing Addresses suggestions from @ellipsis-dev: - Add debouncing to updateCopyButtonPosition for better scroll performance - Add error handling for clipboard operations with visual feedback - Fix React hook dependency warnings Signed-off-by: Eric Wheeler --- .../src/components/common/CodeBlock.tsx | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/webview-ui/src/components/common/CodeBlock.tsx b/webview-ui/src/components/common/CodeBlock.tsx index 777c76d82..a0d925d25 100644 --- a/webview-ui/src/components/common/CodeBlock.tsx +++ b/webview-ui/src/components/common/CodeBlock.tsx @@ -1,4 +1,5 @@ -import { memo, useEffect, useRef, useState } from "react" +import { memo, useEffect, useRef, useState, useCallback } from "react" +import debounce from "debounce" import { useRemark } from "react-remark" import rehypeHighlight, { Options } from "rehype-highlight" import styled from "styled-components" @@ -204,7 +205,7 @@ const CodeBlock = memo(({ source, language, preStyle }: CodeBlockProps) => { }, }) - const updateCopyButtonPosition = (forceShow = false) => { + const updateCopyButtonPosition = useCallback((forceShow = false) => { const codeBlock = codeBlockRef.current if (!codeBlock) return @@ -226,11 +227,12 @@ const CodeBlock = memo(({ source, language, preStyle }: CodeBlockProps) => { codeBlock.style.setProperty("--copy-button-top", `${topPosition}px`) codeBlock.style.setProperty("--copy-button-right", `${rightPosition}px`) } - } + }, []) useEffect(() => { - const handleScroll = () => updateCopyButtonPosition() - const handleResize = () => updateCopyButtonPosition() + const debouncedUpdate = debounce(updateCopyButtonPosition, 10) + const handleScroll = () => debouncedUpdate() + const handleResize = () => debouncedUpdate() const scrollContainer = document.querySelector('[data-virtuoso-scroller="true"]') if (scrollContainer) { @@ -244,25 +246,35 @@ const CodeBlock = memo(({ source, language, preStyle }: CodeBlockProps) => { scrollContainer.removeEventListener("scroll", handleScroll) window.removeEventListener("resize", handleResize) } + debouncedUpdate.clear() } - }, []) + }, [updateCopyButtonPosition]) // Update button position when content changes useEffect(() => { if (reactContent) { // Small delay to ensure content is rendered - setTimeout(() => updateCopyButtonPosition(), 0) + setTimeout(updateCopyButtonPosition, 0) } - }, [reactContent]) + }, [reactContent, updateCopyButtonPosition]) + + const [copyError, setCopyError] = useState(false) const handleCopy = (e: React.MouseEvent) => { e.stopPropagation() if (source) { // Extract code content from markdown code block const codeContent = source.replace(/^```[\s\S]*?\n([\s\S]*?)```$/m, "$1").trim() - navigator.clipboard.writeText(codeContent) - setCopied(true) - setTimeout(() => setCopied(false), 2000) + try { + navigator.clipboard.writeText(codeContent) + setCopied(true) + setCopyError(false) + setTimeout(() => setCopied(false), 2000) + } catch (error) { + console.error("Failed to copy to clipboard:", error) + setCopyError(true) + setTimeout(() => setCopyError(false), 2000) + } } } @@ -280,7 +292,7 @@ const CodeBlock = memo(({ source, language, preStyle }: CodeBlockProps) => { onMouseEnter={() => updateCopyButtonPosition(true)} onMouseLeave={() => updateCopyButtonPosition()}> - +