mirror of
https://github.com/dergigi/boris.git
synced 2025-12-25 10:34:28 +01:00
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:
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user