fix(highlights): preserve immediate UI highlight after creation by merging streaming results instead of overwriting in article and external URL loaders

This commit is contained in:
Gigi
2025-10-20 09:07:42 +02:00
parent 8ff3f08d8c
commit d2181ad772
3 changed files with 14 additions and 35 deletions

View File

@@ -74,19 +74,18 @@ export function useArticleLoader({
try { try {
setHighlightsLoading(true) setHighlightsLoading(true)
setHighlights([]) // Clear old highlights setHighlights([]) // Clear old highlights
const highlightsMap = new Map<string, Highlight>()
await fetchHighlightsForArticle( await fetchHighlightsForArticle(
relayPool, relayPool,
articleCoordinate, articleCoordinate,
article.event.id, article.event.id,
(highlight) => { (highlight) => {
// Deduplicate highlights by ID as they arrive // Merge streaming results with existing UI state to preserve locally created highlights
if (!highlightsMap.has(highlight.id)) { setHighlights((prev) => {
highlightsMap.set(highlight.id, highlight) if (prev.some(h => h.id === highlight.id)) return prev
const highlightsList = Array.from(highlightsMap.values()) const next = [highlight, ...prev]
setHighlights(highlightsList.sort((a, b) => b.created_at - a.created_at)) return next.sort((a, b) => b.created_at - a.created_at)
} })
}, },
settings settings
) )

View File

@@ -87,7 +87,13 @@ export function useExternalUrlLoader({
// Seed with cached highlights first // Seed with cached highlights first
if (cachedUrlHighlights.length > 0) { if (cachedUrlHighlights.length > 0) {
setHighlights(cachedUrlHighlights.sort((a, b) => b.created_at - a.created_at)) setHighlights((prev) => {
// Seed with cache but keep any locally created highlights already in state
const seen = new Set<string>(cachedUrlHighlights.map(h => h.id))
const localOnly = prev.filter(h => !seen.has(h.id))
const next = [...cachedUrlHighlights, ...localOnly]
return next.sort((a, b) => b.created_at - a.created_at)
})
} else { } else {
setHighlights([]) setHighlights([])
} }
@@ -106,7 +112,7 @@ export function useExternalUrlLoader({
seen.add(highlight.id) seen.add(highlight.id)
setHighlights((prev) => { setHighlights((prev) => {
if (prev.some(h => h.id === highlight.id)) return prev if (prev.some(h => h.id === highlight.id)) return prev
const next = [...prev, highlight] const next = [highlight, ...prev]
return next.sort((a, b) => b.created_at - a.created_at) return next.sort((a, b) => b.created_at - a.created_at)
}) })
}, },

View File

@@ -113,32 +113,6 @@ export const useHighlightInteractions = ({
}, 10) }, 10)
}, [onTextSelection, onClearSelection]) }, [onTextSelection, onClearSelection])
// Listen to document-level selection changes to catch keyboard/touch/ios cases
useEffect(() => {
const handler = () => {
// Defer to allow DOM selection to settle
setTimeout(() => {
const selection = window.getSelection()
if (!selection || selection.rangeCount === 0) {
onClearSelection?.()
return
}
const range = selection.getRangeAt(0)
const text = selection.toString().trim()
if (text.length > 0 && contentRef.current?.contains(range.commonAncestorContainer)) {
onTextSelection?.(text)
} else {
onClearSelection?.()
}
}, 10)
}
document.addEventListener('selectionchange', handler)
return () => document.removeEventListener('selectionchange', handler)
}, [onTextSelection, onClearSelection])
return { contentRef, handleSelectionEnd } return { contentRef, handleSelectionEnd }
} }