From 9a10c88c98312a21387c1a2bf95c4979e92ee5a4 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Sat, 3 Jan 2026 10:20:06 -0500 Subject: [PATCH] perf changes --- src/components/blog/PostBodyClient.tsx | 95 ++++++++++++++++---------- src/components/blog/TextEditor.tsx | 11 ++- src/config.ts | 2 +- 3 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/components/blog/PostBodyClient.tsx b/src/components/blog/PostBodyClient.tsx index 14ea319..3fa5439 100644 --- a/src/components/blog/PostBodyClient.tsx +++ b/src/components/blog/PostBodyClient.tsx @@ -115,42 +115,7 @@ export default function PostBodyClient(props: PostBodyClientProps) { copyButton.style.cssText = "background-color: var(--color-surface0); color: var(--color-text); border: 1px solid var(--color-overlay0);"; copyButton.textContent = "Copy"; - - copyButton.addEventListener("mouseenter", () => { - copyButton.style.backgroundColor = "var(--color-surface1)"; - }); - - copyButton.addEventListener("mouseleave", () => { - if (copyButton.textContent === "Copy") { - copyButton.style.backgroundColor = "var(--color-surface0)"; - } - }); - - copyButton.addEventListener("click", async () => { - const code = codeBlock.textContent || ""; - - try { - await navigator.clipboard.writeText(code); - copyButton.textContent = "Copied!"; - copyButton.style.backgroundColor = "var(--color-green)"; - copyButton.style.color = "var(--color-base)"; - - setTimeout(() => { - copyButton.textContent = "Copy"; - copyButton.style.backgroundColor = "var(--color-surface0)"; - copyButton.style.color = "var(--color-text)"; - }, 2000); - } catch (err) { - console.error("Failed to copy code:", err); - copyButton.textContent = "Failed"; - copyButton.style.backgroundColor = "var(--color-red)"; - - setTimeout(() => { - copyButton.textContent = "Copy"; - copyButton.style.backgroundColor = "var(--color-surface0)"; - }, 2000); - } - }); + copyButton.dataset.codeBlock = "true"; // Mark for event delegation pre.appendChild(copyButton); }); @@ -326,6 +291,64 @@ export default function PostBodyClient(props: PostBodyClientProps) { addCopyButtons(); } }, 150); + + // Event delegation for copy buttons (single listener for all buttons) + if (contentRef) { + const handleCopyButtonInteraction = async (e: Event) => { + const target = e.target as HTMLElement; + + // Handle mouseenter + if ( + e.type === "mouseover" && + target.classList.contains("copy-button") + ) { + target.style.backgroundColor = "var(--color-surface1)"; + } + + // Handle mouseleave + if (e.type === "mouseout" && target.classList.contains("copy-button")) { + if (target.textContent === "Copy") { + target.style.backgroundColor = "var(--color-surface0)"; + } + } + + // Handle click + if (e.type === "click" && target.classList.contains("copy-button")) { + const pre = target.parentElement; + const codeBlock = pre?.querySelector("code"); + if (!codeBlock) return; + + const code = codeBlock.textContent || ""; + + try { + await navigator.clipboard.writeText(code); + target.textContent = "Copied!"; + target.style.backgroundColor = "var(--color-green)"; + target.style.color = "var(--color-base)"; + + setTimeout(() => { + target.textContent = "Copy"; + target.style.backgroundColor = "var(--color-surface0)"; + target.style.color = "var(--color-text)"; + }, 2000); + } catch (err) { + console.error("Failed to copy code:", err); + target.textContent = "Failed"; + target.style.backgroundColor = "var(--color-red)"; + + setTimeout(() => { + target.textContent = "Copy"; + target.style.backgroundColor = "var(--color-surface0)"; + }, 2000); + } + } + }; + + // Single event listener for all copy button interactions + contentRef.addEventListener("click", handleCopyButtonInteraction); + contentRef.addEventListener("mouseover", handleCopyButtonInteraction); + contentRef.addEventListener("mouseout", handleCopyButtonInteraction); + } }); createEffect(() => { diff --git a/src/components/blog/TextEditor.tsx b/src/components/blog/TextEditor.tsx index 141345b..1b491e6 100644 --- a/src/components/blog/TextEditor.tsx +++ b/src/components/blog/TextEditor.tsx @@ -838,6 +838,9 @@ export default function TextEditor(props: TextEditorProps) { let isInitialLoad = true; // Flag to prevent capturing history on initial load let hasAttemptedHistoryLoad = false; // Flag to prevent repeated load attempts + // Throttle timer for reference operations + let updateThrottleTimer: ReturnType | null = null; + // LLM Infill state const [currentSuggestion, setCurrentSuggestion] = createSignal(""); const [isInfillLoading, setIsInfillLoading] = createSignal(false); @@ -1717,9 +1720,15 @@ export default function TextEditor(props: TextEditorProps) { onUpdate: ({ editor }) => { untrack(() => { props.updateContent(editor.getHTML()); - setTimeout(() => { + + // Throttle reference operations to reduce DOM thrashing + if (updateThrottleTimer) { + clearTimeout(updateThrottleTimer); + } + updateThrottleTimer = setTimeout(() => { renumberAllReferences(editor); updateReferencesSection(editor); + updateThrottleTimer = null; }, TEXT_EDITOR_CONFIG.REFERENCE_UPDATE_DELAY_MS); // Debounced history capture diff --git a/src/config.ts b/src/config.ts index e79c07b..8b42753 100644 --- a/src/config.ts +++ b/src/config.ts @@ -191,7 +191,7 @@ export const TEXT_EDITOR_CONFIG = { SPINNER_INTERVAL_MS: 50, HIGHLIGHT_FADE_DELAY_MS: 100, HIGHLIGHT_REMOVE_DELAY_MS: 700, - REFERENCE_UPDATE_DELAY_MS: 100, + REFERENCE_UPDATE_DELAY_MS: 500, SCROLL_TO_CHANGE_DELAY_MS: 100 } as const;