diff --git a/src/components/Note/Highlight.tsx b/src/components/Note/Highlight.tsx
deleted file mode 100644
index eca44c6..0000000
--- a/src/components/Note/Highlight.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import { useFetchEvent, useTranslatedEvent } from '@/hooks'
-import { createFakeEvent } from '@/lib/event'
-import { toNote } from '@/lib/link'
-import { isValidPubkey } from '@/lib/pubkey'
-import { generateBech32IdFromATag, generateBech32IdFromETag } from '@/lib/tag'
-import { cn } from '@/lib/utils'
-import { useSecondaryPage } from '@/PageManager'
-import { Event } from 'nostr-tools'
-import { useMemo } from 'react'
-import { useTranslation } from 'react-i18next'
-import Content from '../Content'
-import ContentPreview from '../ContentPreview'
-import UserAvatar from '../UserAvatar'
-
-export default function Highlight({ event, className }: { event: Event; className?: string }) {
- const translatedEvent = useTranslatedEvent(event.id)
- const comment = useMemo(
- () => (translatedEvent?.tags ?? event.tags).find((tag) => tag[0] === 'comment')?.[1],
- [event, translatedEvent]
- )
-
- return (
-
- {comment &&
}
-
-
-
- {translatedEvent?.content ?? event.content}
-
-
-
-
- )
-}
-
-function HighlightSource({ event }: { event: Event }) {
- const { t } = useTranslation()
- const { push } = useSecondaryPage()
- const sourceTag = useMemo(() => {
- let sourceTag: string[] | undefined
- for (const tag of event.tags) {
- if (tag[2] === 'source') {
- sourceTag = tag
- break
- }
- if (tag[0] === 'r') {
- sourceTag = tag
- continue
- } else if (tag[0] === 'a') {
- if (!sourceTag || sourceTag[0] !== 'r') {
- sourceTag = tag
- }
- continue
- } else if (tag[0] === 'e') {
- if (!sourceTag || sourceTag[0] === 'e') {
- sourceTag = tag
- }
- continue
- }
- }
-
- return sourceTag
- }, [event])
- const { event: referenceEvent } = useFetchEvent(
- sourceTag
- ? sourceTag[0] === 'e'
- ? generateBech32IdFromETag(sourceTag)
- : sourceTag[0] === 'a'
- ? generateBech32IdFromATag(sourceTag)
- : undefined
- : undefined
- )
- const referenceEventId = useMemo(() => {
- if (!sourceTag || sourceTag[0] === 'r') return
- if (sourceTag[0] === 'e') {
- return sourceTag[1]
- }
- if (sourceTag[0] === 'a') {
- return generateBech32IdFromATag(sourceTag)
- }
- }, [sourceTag])
- const pubkey = useMemo(() => {
- if (referenceEvent) {
- return referenceEvent.pubkey
- }
- if (sourceTag && sourceTag[0] === 'a') {
- const [, pubkey] = sourceTag[1].split(':')
- if (isValidPubkey(pubkey)) {
- return pubkey
- }
- }
- }, [sourceTag, referenceEvent])
-
- if (!sourceTag) {
- return null
- }
-
- if (sourceTag[0] === 'r') {
- return (
-
- )
- }
-
- return (
-
-
{t('From')}
- {pubkey &&
}
- {referenceEventId && (
-
{
- e.stopPropagation()
- push(toNote(referenceEvent ?? referenceEventId))
- }}
- >
- {referenceEvent ? : referenceEventId}
-
- )}
-
- )
-}
diff --git a/src/components/Note/Highlight/index.tsx b/src/components/Note/Highlight/index.tsx
index 67e5b3c..5e928ab 100644
--- a/src/components/Note/Highlight/index.tsx
+++ b/src/components/Note/Highlight/index.tsx
@@ -1,7 +1,6 @@
import { SecondaryPageLink } from '@/PageManager'
import { Event } from 'nostr-tools'
import { ExternalLink, Highlighter } from 'lucide-react'
-import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { nip19 } from 'nostr-tools'
import { toNote } from '@/lib/link'
@@ -13,103 +12,127 @@ export default function Highlight({
event: Event
className?: string
}) {
- const { t } = useTranslation()
+ try {
+ const { t } = useTranslation()
- // Extract the source (e-tag, a-tag, or r-tag)
- const source = useMemo(() => {
+ // Extract the source (e-tag, a-tag, or r-tag) - simplified without useMemo
+ let source = null
const eTag = event.tags.find(tag => tag[0] === 'e')
if (eTag) {
const eventId = eTag[1]
- return {
+ source = {
type: 'event' as const,
value: eventId,
bech32: nip19.noteEncode(eventId)
}
- }
-
- const aTag = event.tags.find(tag => tag[0] === 'a')
- if (aTag) {
- const [kind, pubkey, identifier] = aTag[1].split(':')
- const relay = aTag[2]
- return {
- type: 'addressable' as const,
- value: aTag[1],
- bech32: nip19.naddrEncode({
- kind: parseInt(kind),
- pubkey,
- identifier: identifier || '',
- relays: relay ? [relay] : []
- })
- }
- }
-
- const rTag = event.tags.find(tag => tag[0] === 'r' && tag[2] === 'source')
- if (rTag) {
- return {
- type: 'url' as const,
- value: rTag[1],
- bech32: rTag[1]
+ } else {
+ const aTag = event.tags.find(tag => tag[0] === 'a')
+ if (aTag) {
+ const [kind, pubkey, identifier] = aTag[1].split(':')
+ const relay = aTag[2]
+ source = {
+ type: 'addressable' as const,
+ value: aTag[1],
+ bech32: nip19.naddrEncode({
+ kind: parseInt(kind),
+ pubkey,
+ identifier: identifier || '',
+ relays: relay ? [relay] : []
+ })
+ }
+ } else {
+ const rTag = event.tags.find(tag => tag[0] === 'r' && tag[2] === 'source')
+ if (rTag) {
+ source = {
+ type: 'url' as const,
+ value: rTag[1],
+ bech32: rTag[1]
+ }
+ }
}
}
- return null
- }, [event.tags])
-
- // Extract the context (optional comment/surrounding context)
- const context = useMemo(() => {
+ // Extract the context (the main quote/full text being highlighted from)
const contextTag = event.tags.find(tag => tag[0] === 'context')
- return contextTag?.[1] || ''
- }, [event.tags])
+ const context = contextTag?.[1] || event.content // Default to content if no context
+
+ // The event.content is the highlighted portion
+ const highlightedText = event.content
- return (
-
-
-
+ return (
+
- {/* Highlighted text */}
- {event.content && (
-
- "{event.content}"
-
- )}
+ {/* Full quoted text with highlighted portion */}
+ {context && (
+
+ {contextTag && highlightedText ? (
+ // If we have both context and highlighted text, show the highlight within the context
+
+ {context.split(highlightedText).map((part, index) => (
+
+ {part}
+ {index < context.split(highlightedText).length - 1 && (
+
+ {highlightedText}
+
+ )}
+
+ ))}
+
+ ) : (
+ // If no context tag, just show the content as a regular quote
+
+ "{context}"
+
+ )}
+
+ )}
- {/* Context (user's comment or surrounding context) - rendered as plaintext */}
- {context && (
-
- {context}
-
- )}
-
- {/* Source link */}
- {source && (
-
- )}
+ {/* Source link */}
+ {source && (
+
+ )}
+
+
+ )
+ } catch (error) {
+ console.error('Highlight component error:', error)
+ return (
+
+
+
+
+
Highlight Error:
+
{String(error)}
+
Content: {event.content}
+
Context: {event.tags.find(tag => tag[0] === 'context')?.[1] || 'No context found'}
+
-
- )
+ )
+ }
}
diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx
index 126d414..672706f 100644
--- a/src/components/Note/index.tsx
+++ b/src/components/Note/index.tsx
@@ -22,6 +22,7 @@ import CommunityDefinition from './CommunityDefinition'
import DiscussionContent from './DiscussionContent'
import GroupMetadata from './GroupMetadata'
import Highlight from './Highlight'
+
import IValue from './IValue'
import LiveEvent from './LiveEvent'
import LongFormArticle from './LongFormArticle'
@@ -62,24 +63,38 @@ export default function Note({
const [showMuted, setShowMuted] = useState(false)
let content: React.ReactNode
- if (
- ![
- ...SUPPORTED_KINDS,
- kinds.CommunityDefinition,
- kinds.LiveEvent,
- ExtendedKind.GROUP_METADATA,
- ExtendedKind.PUBLIC_MESSAGE,
- ExtendedKind.ZAP_REQUEST,
- ExtendedKind.ZAP_RECEIPT
- ].includes(event.kind)
- ) {
+
+ const supportedKindsList = [
+ ...SUPPORTED_KINDS,
+ kinds.CommunityDefinition,
+ kinds.LiveEvent,
+ ExtendedKind.GROUP_METADATA,
+ ExtendedKind.PUBLIC_MESSAGE,
+ ExtendedKind.ZAP_REQUEST,
+ ExtendedKind.ZAP_RECEIPT
+ ]
+
+
+ if (!supportedKindsList.includes(event.kind)) {
+ console.log('Note component - rendering UnknownNote for unsupported kind:', event.kind)
content =
} else if (mutePubkeySet.has(event.pubkey) && !showMuted) {
content =
setShowMuted(true)} />
} else if (!defaultShowNsfw && isNsfwEvent(event) && !showNsfw) {
content = setShowNsfw(true)} />
} else if (event.kind === kinds.Highlights) {
- content =
+ // Try to render the Highlight component with error boundary
+ try {
+ content =
+ } catch (error) {
+ console.error('Note component - Error rendering Highlight component:', error)
+ content =
+
HIGHLIGHT ERROR:
+
Error: {String(error)}
+
Content: {event.content}
+
Context: {event.tags.find(tag => tag[0] === 'context')?.[1] || 'No context found'}
+
+ }
} else if (event.kind === kinds.LongFormArticle) {
content = showFull ? (
diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx
index 49a5734..dacddb0 100644
--- a/src/components/NoteList/index.tsx
+++ b/src/components/NoteList/index.tsx
@@ -153,10 +153,11 @@ const NoteList = forwardRef(
useImperativeHandle(ref, () => ({ scrollToTop, refresh }), [])
- useEffect(() => {
- if (!subRequests.length) return
+ useEffect(() => {
+ if (!subRequests.length) return
- async function init() {
+ async function init() {
+
setLoading(true)
setEvents([])
setNewEvents([])
diff --git a/src/components/PostEditor/HighlightEditor.tsx b/src/components/PostEditor/HighlightEditor.tsx
index 0954c95..5a22b99 100644
--- a/src/components/PostEditor/HighlightEditor.tsx
+++ b/src/components/PostEditor/HighlightEditor.tsx
@@ -11,7 +11,7 @@ export interface HighlightData {
sourceType: 'nostr' | 'url'
sourceValue: string // nevent/naddr/note/hex for nostr, https:// URL for url
sourceHexId?: string // converted hex ID for nostr sources
- description?: string // optional comment/description
+ context?: string // the full text/quote that the highlight is from
}
interface HighlightEditorProps {
@@ -27,7 +27,7 @@ export default function HighlightEditor({
}: HighlightEditorProps) {
const { t } = useTranslation()
const [sourceInput, setSourceInput] = useState(highlightData.sourceValue)
- const [description, setDescription] = useState(highlightData.description || '')
+ const [context, setContext] = useState(highlightData.context || '')
const [error, setError] = useState('')
// Validate and parse the source input
@@ -43,7 +43,7 @@ export default function HighlightEditor({
setHighlightData({
sourceType: 'url',
sourceValue: sourceInput,
- description
+ context
})
return
}
@@ -60,7 +60,7 @@ export default function HighlightEditor({
sourceType: 'nostr',
sourceValue: sourceInput,
sourceHexId: hexId,
- description
+ context
})
return
}
@@ -75,7 +75,7 @@ export default function HighlightEditor({
sourceType: 'nostr',
sourceValue: sourceInput, // Keep original for reference
sourceHexId: hexId, // Store the hex ID
- description
+ context
})
} else if (decoded.type === 'nevent') {
hexId = decoded.data.id
@@ -84,7 +84,7 @@ export default function HighlightEditor({
sourceType: 'nostr',
sourceValue: sourceInput, // Keep the nevent for relay info
sourceHexId: hexId, // Store the hex ID
- description
+ context
})
} else if (decoded.type === 'naddr') {
// For naddr, we need to keep the full naddr string to extract kind:pubkey:identifier
@@ -93,7 +93,7 @@ export default function HighlightEditor({
sourceType: 'nostr',
sourceValue: sourceInput, // Keep the naddr for a-tag building
sourceHexId: undefined, // No hex ID for addressable events
- description
+ context
})
} else {
setError(t('Invalid source. Please enter a note ID, nevent, naddr, hex ID, or URL.'))
@@ -102,7 +102,7 @@ export default function HighlightEditor({
} catch (err) {
setError(t('Invalid source. Please enter a note ID, nevent, naddr, hex ID, or URL.'))
}
- }, [sourceInput, description, setHighlightData, t])
+ }, [sourceInput, context, setHighlightData, t])
return (
@@ -139,26 +139,28 @@ export default function HighlightEditor({
-
{t('About Highlights (NIP-84)')}
-
- {t('The highlighted text goes in the main content. The source and optional context will be added as tags.')}
-
+
{t('How to Create a Highlight (NIP-84)')}
+
+ - {t('Enter the specific text you want to highlight in the main content area above')}
+ - {t('Add the source (where this text is from)')}
+ - {t('Optionally, add the full quote/context to show your highlight within it')}
+
)
diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx
index 6cb6822..44c86e6 100644
--- a/src/components/PostEditor/PostContent.tsx
+++ b/src/components/PostEditor/PostContent.tsx
@@ -62,8 +62,7 @@ export default function PostContent({
const [isHighlight, setIsHighlight] = useState(false)
const [highlightData, setHighlightData] = useState({
sourceType: 'nostr',
- sourceValue: '',
- description: ''
+ sourceValue: ''
})
const [pollCreateData, setPollCreateData] = useState({
isMultipleChoice: false,
@@ -201,16 +200,17 @@ export default function PostContent({
if (isHighlight) {
// For highlights, pass the original sourceValue which contains the full identifier
// The createHighlightDraftEvent function will parse it correctly
- draftEvent = await createHighlightDraftEvent(
- text,
- highlightData.sourceType,
- highlightData.sourceValue,
- highlightData.description,
- {
- addClientTag,
- isNsfw
- }
- )
+ draftEvent = await createHighlightDraftEvent(
+ text,
+ highlightData.sourceType,
+ highlightData.sourceValue,
+ highlightData.context,
+ undefined, // description parameter (not used)
+ {
+ addClientTag,
+ isNsfw
+ }
+ )
} else if (isPublicMessage) {
draftEvent = await createPublicMessageDraftEvent(text, publicMessageRecipients, {
addClientTag,
diff --git a/src/lib/draft-event.ts b/src/lib/draft-event.ts
index 6e31891..69d700e 100644
--- a/src/lib/draft-event.ts
+++ b/src/lib/draft-event.ts
@@ -885,6 +885,7 @@ export async function createHighlightDraftEvent(
highlightedText: string,
sourceType: 'nostr' | 'url',
sourceValue: string,
+ context?: string, // The full text/quote that the highlight is from
description?: string,
options?: {
addClientTag?: boolean
@@ -969,10 +970,14 @@ export async function createHighlightDraftEvent(
tags.push(['r', sourceValue, 'source'])
}
- // Add context tag if provided (user's comment about the highlight)
- // NIP-84 specifies using 'context' for additional context around the highlight
+ // Add context tag if provided (the full text/quote that the highlight is from)
+ if (context && context.trim()) {
+ tags.push(['context', context.trim()])
+ }
+
+ // Add description tag if provided (user's explanation/comment)
if (description && description.trim()) {
- tags.push(['context', description.trim()])
+ tags.push(['description', description.trim()])
}
// Add p-tag for the author of the source material (if we can determine it)