From eadab9a37f23672195be48d5e627ad64d29291dd Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 00:19:45 +0200 Subject: [PATCH] fix(highlights): restore scroll-to-highlight functionality with MutationObserver - Add MutationObserver to detect when highlights are added/removed from DOM - Use contentVersion state to trigger re-attachment of click handlers - Add contentVersion dependency to scroll effect so it re-runs after DOM updates - Add 100ms delay to scroll effect to ensure DOM is fully rendered - Add warning log when mark element cannot be found - Fixes issue where clicking highlights in sidebar wouldn't scroll after DOM changes --- src/hooks/useHighlightInteractions.ts | 54 ++++++++++++++++++++------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/hooks/useHighlightInteractions.ts b/src/hooks/useHighlightInteractions.ts index c4bb5351..baf9d90f 100644 --- a/src/hooks/useHighlightInteractions.ts +++ b/src/hooks/useHighlightInteractions.ts @@ -1,4 +1,4 @@ -import { useEffect, useCallback, useRef } from 'react' +import { useEffect, useCallback, useRef, useState } from 'react' interface UseHighlightInteractionsParams { onHighlightClick?: (highlightId: string) => void @@ -14,6 +14,25 @@ export const useHighlightInteractions = ({ onClearSelection }: UseHighlightInteractionsParams) => { const contentRef = useRef(null) + const [contentVersion, setContentVersion] = useState(0) + + // Watch for DOM changes (highlights being added/removed) + useEffect(() => { + if (!contentRef.current) return + + const observer = new MutationObserver(() => { + // Increment version to trigger re-attachment of handlers + setContentVersion(prev => prev + 1) + }) + + observer.observe(contentRef.current, { + childList: true, + subtree: true, + characterData: false + }) + + return () => observer.disconnect() + }, []) // Attach click handlers to highlight marks useEffect(() => { @@ -37,24 +56,33 @@ export const useHighlightInteractions = ({ mark.removeEventListener('click', handler) }) } - }, [onHighlightClick]) + }, [onHighlightClick, contentVersion]) // Scroll to selected highlight useEffect(() => { if (!selectedHighlightId || !contentRef.current) return - const markElement = contentRef.current.querySelector(`mark[data-highlight-id="${selectedHighlightId}"]`) - - if (markElement) { - markElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) + // Use a small delay to ensure DOM is updated + const timeoutId = setTimeout(() => { + if (!contentRef.current) return - const htmlElement = markElement as HTMLElement - setTimeout(() => { - htmlElement.classList.add('highlight-pulse') - setTimeout(() => htmlElement.classList.remove('highlight-pulse'), 1500) - }, 500) - } - }, [selectedHighlightId]) + const markElement = contentRef.current.querySelector(`mark[data-highlight-id="${selectedHighlightId}"]`) + + if (markElement) { + markElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) + + const htmlElement = markElement as HTMLElement + setTimeout(() => { + htmlElement.classList.add('highlight-pulse') + setTimeout(() => htmlElement.classList.remove('highlight-pulse'), 1500) + }, 500) + } else { + console.warn('Could not find mark element for highlight:', selectedHighlightId) + } + }, 100) + + return () => clearTimeout(timeoutId) + }, [selectedHighlightId, contentVersion]) // Handle text selection (works for both mouse and touch) const handleSelectionEnd = useCallback(() => {