fix: improve highlight rendering pipeline with comprehensive debugging

- Add extensive logging to track highlight rendering through entire pipeline
- Fix markdown rendering to wait for HTML conversion before displaying
- Prevent fallback to non-highlighted markdown during initial render
- Add debugging to URL filtering to identify matching issues
- Add logging to highlight application to track matching success/failures
- Ensure highlights are always applied when content is ready
- Show mini loading spinner while markdown is being converted

This will help diagnose and fix cases where highlights aren't showing up.
This commit is contained in:
Gigi
2025-10-06 19:56:20 +01:00
parent 872d38c7f3
commit d9db10fd70
4 changed files with 99 additions and 25 deletions

2
dist/index.html vendored
View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Boris - Nostr Bookmarks</title>
<script type="module" crossorigin src="/assets/index-CsB0QtFa.js"></script>
<script type="module" crossorigin src="/assets/index-rEUBRPdE.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bqz-n1DY.css">
</head>
<body>

View File

@@ -58,10 +58,17 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
// Filter highlights by URL and visibility settings
const relevantHighlights = useMemo(() => {
console.log('🔍 ContentPanel: Processing highlights', {
totalHighlights: highlights.length,
selectedUrl,
showHighlights
})
const urlFiltered = filterHighlightsByUrl(highlights, selectedUrl)
console.log('📌 URL filtered highlights:', urlFiltered.length)
// Apply visibility filtering
return urlFiltered
const filtered = urlFiltered
.map(h => {
// Classify highlight level
let level: 'mine' | 'friends' | 'nostrverse' = 'nostrverse'
@@ -78,7 +85,10 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
if (h.level === 'friends') return highlightVisibility.friends
return highlightVisibility.nostrverse
})
}, [selectedUrl, highlights, highlightVisibility, currentUserPubkey, followedPubkeys])
console.log('✅ Relevant highlights after filtering:', filtered.length, filtered.map(h => h.content.substring(0, 30)))
return filtered
}, [selectedUrl, highlights, highlightVisibility, currentUserPubkey, followedPubkeys, showHighlights])
// Convert markdown to HTML when markdown content changes
useEffect(() => {
@@ -87,10 +97,16 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
return
}
console.log('📝 Converting markdown to HTML...')
// Use requestAnimationFrame to ensure ReactMarkdown has rendered
const rafId = requestAnimationFrame(() => {
if (markdownPreviewRef.current) {
setRenderedHtml(markdownPreviewRef.current.innerHTML)
const html = markdownPreviewRef.current.innerHTML
console.log('✅ Markdown converted to HTML:', html.length, 'chars')
setRenderedHtml(html)
} else {
console.warn('⚠️ markdownPreviewRef.current is null')
}
})
@@ -100,13 +116,30 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
// Prepare the final HTML with highlights applied
const finalHtml = useMemo(() => {
const sourceHtml = markdown ? renderedHtml : html
if (!sourceHtml) return ''
console.log('🎨 Preparing final HTML:', {
hasMarkdown: !!markdown,
hasHtml: !!html,
renderedHtmlLength: renderedHtml.length,
sourceHtmlLength: sourceHtml?.length || 0,
showHighlights,
relevantHighlightsCount: relevantHighlights.length
})
if (!sourceHtml) {
console.warn('⚠️ No source HTML available')
return ''
}
// Apply highlights if we have them and highlights are enabled
if (showHighlights && relevantHighlights.length > 0) {
return applyHighlightsToHTML(sourceHtml, relevantHighlights, highlightStyle)
console.log('✨ Applying', relevantHighlights.length, 'highlights to HTML')
const highlightedHtml = applyHighlightsToHTML(sourceHtml, relevantHighlights, highlightStyle)
console.log('✅ Highlights applied, result length:', highlightedHtml.length)
return highlightedHtml
}
console.log('📄 Returning source HTML without highlights')
return sourceHtml
}, [html, renderedHtml, markdown, relevantHighlights, showHighlights, highlightStyle])
@@ -224,7 +257,8 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
/>
{markdown || html ? (
markdown ? (
finalHtml ? (
// For markdown, always use finalHtml once it's ready to ensure highlights are applied
renderedHtml && finalHtml ? (
<div
ref={contentRef}
className="reader-markdown"
@@ -232,17 +266,15 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
onMouseUp={handleMouseUp}
/>
) : (
<div
ref={contentRef}
className="reader-markdown"
onMouseUp={handleMouseUp}
>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{markdown}
</ReactMarkdown>
// Show loading state while markdown is being converted to HTML
<div className="reader-markdown">
<div className="loading-spinner">
<FontAwesomeIcon icon={faSpinner} spin size="sm" />
</div>
</div>
)
) : (
// For HTML, use finalHtml directly
<div
ref={contentRef}
className="reader-html"

View File

@@ -144,8 +144,6 @@ function tryMarkInTextNodes(
if (index === -1) continue
console.log(`✅ Found ${useNormalized ? 'normalized' : 'exact'} match:`, text.slice(0, 50))
let actualIndex = index
if (useNormalized) {
// Map normalized index back to original text
@@ -172,14 +170,26 @@ function tryMarkInTextNodes(
* Apply highlights to HTML content by injecting mark tags using DOM manipulation
*/
export function applyHighlightsToHTML(html: string, highlights: Highlight[], highlightStyle: 'marker' | 'underline' = 'marker'): string {
if (!html || highlights.length === 0) return html
if (!html || highlights.length === 0) {
console.log('⚠️ applyHighlightsToHTML: No HTML or highlights', { htmlLength: html?.length, highlightsCount: highlights.length })
return html
}
console.log('🔨 applyHighlightsToHTML: Processing', highlights.length, 'highlights')
const tempDiv = document.createElement('div')
tempDiv.innerHTML = html
let appliedCount = 0
for (const highlight of highlights) {
const searchText = highlight.content.trim()
if (!searchText) continue
if (!searchText) {
console.warn('⚠️ Empty highlight content:', highlight.id)
continue
}
console.log('🔍 Searching for highlight:', searchText.substring(0, 50) + '...')
// Collect all text nodes
const walker = document.createTreeWalker(tempDiv, NodeFilter.SHOW_TEXT, null)
@@ -187,10 +197,21 @@ export function applyHighlightsToHTML(html: string, highlights: Highlight[], hig
let node: Node | null
while ((node = walker.nextNode())) textNodes.push(node as Text)
console.log('📄 Found', textNodes.length, 'text nodes to search')
// Try exact match first, then normalized match
tryMarkInTextNodes(textNodes, searchText, highlight, false, highlightStyle) ||
tryMarkInTextNodes(textNodes, searchText, highlight, true, highlightStyle)
const found = tryMarkInTextNodes(textNodes, searchText, highlight, false, highlightStyle) ||
tryMarkInTextNodes(textNodes, searchText, highlight, true, highlightStyle)
if (found) {
appliedCount++
console.log('✅ Highlight applied successfully')
} else {
console.warn('❌ Could not find match for highlight:', searchText.substring(0, 50))
}
}
console.log('🎉 Applied', appliedCount, '/', highlights.length, 'highlights')
return tempDiv.innerHTML
}

View File

@@ -10,22 +10,43 @@ export function normalizeUrl(url: string): string {
}
export function filterHighlightsByUrl(highlights: Highlight[], selectedUrl: string | undefined): Highlight[] {
if (!selectedUrl || highlights.length === 0) return []
if (!selectedUrl || highlights.length === 0) {
console.log('🔍 filterHighlightsByUrl: No URL or highlights', { selectedUrl, count: highlights.length })
return []
}
console.log('🔍 filterHighlightsByUrl:', { selectedUrl, totalHighlights: highlights.length })
// For Nostr articles, we already fetched highlights specifically for this article
// So we don't need to filter them - they're all relevant
if (selectedUrl.startsWith('nostr:')) {
console.log('📌 Nostr article - returning all', highlights.length, 'highlights')
return highlights
}
// For web URLs, filter by URL matching
const normalizedSelected = normalizeUrl(selectedUrl)
console.log('🔗 Normalized selected URL:', normalizedSelected)
return highlights.filter(h => {
if (!h.urlReference) return false
const filtered = highlights.filter(h => {
if (!h.urlReference) {
console.log('⚠️ Highlight has no urlReference:', h.id, 'eventReference:', h.eventReference)
return false
}
const normalizedRef = normalizeUrl(h.urlReference)
return normalizedSelected === normalizedRef ||
const matches = normalizedSelected === normalizedRef ||
normalizedSelected.includes(normalizedRef) ||
normalizedRef.includes(normalizedSelected)
if (matches) {
console.log('✅ URL match:', normalizedRef)
} else {
console.log('❌ URL mismatch:', normalizedRef, 'vs', normalizedSelected)
}
return matches
})
console.log('📊 Filtered to', filtered.length, 'highlights')
return filtered
}