Browse Source

fix markdown for hashtags and nostr addresses

end search loop
imwald
Silberengel 1 month ago
parent
commit
d9558982e5
  1. 39
      src/components/Note/MarkdownArticle/MarkdownArticle.tsx
  2. 22
      src/components/ParentNotePreview/index.tsx
  3. 4
      src/services/client.service.ts

39
src/components/Note/MarkdownArticle/MarkdownArticle.tsx

@ -1355,15 +1355,14 @@ function parseMarkdownContent( @@ -1355,15 +1355,14 @@ function parseMarkdownContent(
// 4. There's text on the same line before it (part of a sentence)
// 5. There's text before it (even on previous lines, as long as no paragraph break)
shouldMergeHashtag = lineHasOnlyHashtags || hasOtherHashtagsOnLine || hasHashtagsOnAdjacentLines || hasTextOnSameLine || hasTextBefore
// If none of the above, but there's text after the hashtag on the same line, also merge
// This handles cases where hashtag is at start of line but followed by text (e.g. "#pyramid 1.1 has...")
if (!shouldMergeHashtag) {
const textAfterOnSameLine = content.substring(pattern.end, lineEndIndex)
hasTextAfterOnSameLine = textAfterOnSameLine.trim().length > 0
if (hasTextAfterOnSameLine) {
shouldMergeHashtag = true
}
// Always compute — merge branch 2 below needs this even when shouldMergeHashtag was already
// true from hasOtherHashtagsOnLine (e.g. "#a #b word" is not "only hashtags" so branch 1 skips,
// and without hasTextAfterOnSameLine branch 2 would not run → spurious line break before <p>).
const textAfterOnSameLineRaw = content.substring(pattern.end, lineEndIndex)
hasTextAfterOnSameLine = textAfterOnSameLineRaw.trim().length > 0
if (!shouldMergeHashtag && hasTextAfterOnSameLine) {
shouldMergeHashtag = true
}
}
@ -1483,23 +1482,21 @@ function parseMarkdownContent( @@ -1483,23 +1482,21 @@ function parseMarkdownContent(
// Mark this pattern as merged so we don't render it separately later
mergedPatterns.add(patternIdx)
} else if (pattern.type === 'nostr' && (hasTextOnSameLine || hasTextBefore)) {
// Only merge profile types (npub/nprofile) inline; event types (note/nevent/naddr) remain block-level
} else if (pattern.type === 'nostr') {
// Only merge profile types (npub/nprofile) inline; event types (note/nevent/naddr) remain block-level.
// Same idea as hashtags: if the mention is first on the line but more text follows on that line,
// merge into the paragraph — otherwise we emit a bare <span> and the rest in <p>, which looks
// like a spurious hard return (block <p> after inline-block mention).
const bech32Id = pattern.data
const isProfileType = bech32Id.startsWith('npub') || bech32Id.startsWith('nprofile')
if (isProfileType) {
// Get the original pattern syntax from the content
const hasTextAfterNostrOnSameLine =
isProfileType && content.substring(pattern.end, lineEndIndex).trim().length > 0
if (isProfileType && (hasTextOnSameLine || hasTextBefore || hasTextAfterNostrOnSameLine)) {
const patternMarkdown = content.substring(pattern.index, pattern.end)
// Get text after the pattern on the same line
const textAfterPattern = content.substring(pattern.end, lineEndIndex)
// Extend the text to include the pattern and any text after it on the same line
text = text + patternMarkdown + textAfterPattern
textEndIndex = lineEndIndex === content.length ? content.length : lineEndIndex + 1
// Mark this pattern as merged so we don't render it separately later
mergedPatterns.add(patternIdx)
}
}
@ -2155,7 +2152,7 @@ function parseMarkdownContent( @@ -2155,7 +2152,7 @@ function parseMarkdownContent(
// Check if it's a profile type (mentions/handles should be inline)
if (bech32Id.startsWith('npub') || bech32Id.startsWith('nprofile')) {
parts.push(
<span key={`nostr-${patternIdx}`} className="inline-block">
<span key={`nostr-${patternIdx}`} className="inline">
<EmbeddedMention userId={bech32Id} />
</span>
)

22
src/components/ParentNotePreview/index.tsx

@ -4,7 +4,7 @@ import { useFetchEvent } from '@/hooks' @@ -4,7 +4,7 @@ import { useFetchEvent } from '@/hooks'
import { cn } from '@/lib/utils'
import client from '@/services/client.service'
import { useTranslation } from 'react-i18next'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Event, nip19 } from 'nostr-tools'
import ContentPreview from '../ContentPreview'
import UserAvatar from '../UserAvatar'
@ -23,6 +23,8 @@ export default function ParentNotePreview({ @@ -23,6 +23,8 @@ export default function ParentNotePreview({
const { event, isFetching } = useFetchEvent(eventId)
const [fallbackEvent, setFallbackEvent] = useState<Event | undefined>(undefined)
const [isFetchingFallback, setIsFetchingFallback] = useState(false)
/** One automatic searchable-relay attempt per eventId; without this, the effect re-fires forever after each 20s timeout. */
const autoSearchableAttemptedRef = useRef(false)
// Helper function to decode event ID
const getHexEventId = (id: string): string | null => {
@ -62,10 +64,22 @@ export default function ParentNotePreview({ @@ -62,10 +64,22 @@ export default function ParentNotePreview({
}
}, [eventId])
// If the initial fetch fails, try fetching from searchable relays automatically
useEffect(() => {
if (!isFetching && !event && !fallbackEvent && !isFetchingFallback && eventId) {
fetchFromSearchableRelays()
autoSearchableAttemptedRef.current = false
}, [eventId])
// If the initial fetch fails, try searchable relays once (manual retry still works via onClick).
useEffect(() => {
if (
!isFetching &&
!event &&
!fallbackEvent &&
!isFetchingFallback &&
eventId &&
!autoSearchableAttemptedRef.current
) {
autoSearchableAttemptedRef.current = true
void fetchFromSearchableRelays()
}
}, [isFetching, event, eventId, fallbackEvent, isFetchingFallback, fetchFromSearchableRelays])

4
src/services/client.service.ts

@ -2720,7 +2720,7 @@ class ClientService extends EventTarget { @@ -2720,7 +2720,7 @@ class ClientService extends EventTarget {
return undefined
}
logger.info('fetchEventWithExternalRelays: Starting search', {
logger.debug('fetchEventWithExternalRelays: Starting search', {
eventId: eventId.substring(0, 8),
relayCount: externalRelays.length,
relays: externalRelays
@ -2741,7 +2741,7 @@ class ClientService extends EventTarget { @@ -2741,7 +2741,7 @@ class ClientService extends EventTarget {
)
const duration = Date.now() - startTime
logger.info('fetchEventWithExternalRelays: Search completed', {
logger.debug('fetchEventWithExternalRelays: Search completed', {
eventId: eventId.substring(0, 8),
relayCount: externalRelays.length,
eventsFound: events.length,

Loading…
Cancel
Save