mirror of
https://github.com/dergigi/boris.git
synced 2026-01-08 17:34:52 +01:00
- Create highlightMatching utility to find and apply highlights to text/HTML - Update ContentPanel to accept highlights and match them to current URL - Add visual highlighting with yellow background and blue underline - Show highlight count indicator when content has highlights - Add hover effects and tooltips showing highlight date - Support both HTML and markdown content highlighting Highlighted text now appears underlined in the main content panel when viewing URLs that have associated NIP-84 highlights.
112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
import React, { useMemo } from 'react'
|
|
import ReactMarkdown from 'react-markdown'
|
|
import remarkGfm from 'remark-gfm'
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
import { faSpinner, faHighlighter } from '@fortawesome/free-solid-svg-icons'
|
|
import { Highlight } from '../types/highlights'
|
|
import { applyHighlightsToText, applyHighlightsToHTML } from '../utils/highlightMatching'
|
|
|
|
interface ContentPanelProps {
|
|
loading: boolean
|
|
title?: string
|
|
html?: string
|
|
markdown?: string
|
|
selectedUrl?: string
|
|
highlights?: Highlight[]
|
|
}
|
|
|
|
const ContentPanel: React.FC<ContentPanelProps> = ({
|
|
loading,
|
|
title,
|
|
html,
|
|
markdown,
|
|
selectedUrl,
|
|
highlights = []
|
|
}) => {
|
|
// Filter highlights relevant to the current URL
|
|
const relevantHighlights = useMemo(() => {
|
|
if (!selectedUrl || highlights.length === 0) return []
|
|
|
|
return highlights.filter(h => {
|
|
// Match by URL reference
|
|
if (h.urlReference && selectedUrl.includes(h.urlReference)) return true
|
|
if (h.urlReference && h.urlReference.includes(selectedUrl)) return true
|
|
|
|
// Normalize URLs for comparison (remove trailing slashes, protocols)
|
|
const normalizeUrl = (url: string) =>
|
|
url.replace(/^https?:\/\//, '').replace(/\/$/, '').toLowerCase()
|
|
|
|
const normalizedSelected = normalizeUrl(selectedUrl)
|
|
const normalizedRef = h.urlReference ? normalizeUrl(h.urlReference) : ''
|
|
|
|
return normalizedSelected === normalizedRef
|
|
})
|
|
}, [selectedUrl, highlights])
|
|
|
|
// Apply highlights to content
|
|
const highlightedHTML = useMemo(() => {
|
|
if (!html || relevantHighlights.length === 0) return html
|
|
return applyHighlightsToHTML(html, relevantHighlights)
|
|
}, [html, relevantHighlights])
|
|
|
|
const highlightedMarkdown = useMemo(() => {
|
|
if (!markdown || relevantHighlights.length === 0) return markdown
|
|
// For markdown, we'll apply highlights after rendering
|
|
return markdown
|
|
}, [markdown, relevantHighlights])
|
|
|
|
if (!selectedUrl) {
|
|
return (
|
|
<div className="reader empty">
|
|
<p>Select a bookmark to read its content.</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="reader loading">
|
|
<div className="loading-spinner">
|
|
<FontAwesomeIcon icon={faSpinner} spin />
|
|
<span>Loading content…</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const hasHighlights = relevantHighlights.length > 0
|
|
|
|
return (
|
|
<div className="reader">
|
|
{title && (
|
|
<div className="reader-header">
|
|
<h2 className="reader-title">{title}</h2>
|
|
{hasHighlights && (
|
|
<div className="highlight-indicator">
|
|
<FontAwesomeIcon icon={faHighlighter} />
|
|
<span>{relevantHighlights.length} highlight{relevantHighlights.length !== 1 ? 's' : ''}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
{markdown ? (
|
|
<div className="reader-markdown">
|
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
{highlightedMarkdown}
|
|
</ReactMarkdown>
|
|
</div>
|
|
) : highlightedHTML ? (
|
|
<div className="reader-html" dangerouslySetInnerHTML={{ __html: highlightedHTML }} />
|
|
) : (
|
|
<div className="reader empty">
|
|
<p>No readable content found for this URL.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ContentPanel
|
|
|
|
|