Browse Source

render inline mentions

imwald
Silberengel 4 months ago
parent
commit
cf0927830a
  1. 77
      src/components/Note/MarkdownArticle/MarkdownArticle.tsx

77
src/components/Note/MarkdownArticle/MarkdownArticle.tsx

@ -730,10 +730,11 @@ function parseMarkdownContent( @@ -730,10 +730,11 @@ function parseMarkdownContent(
let text = pattern.index > lastIndex ? content.slice(lastIndex, pattern.index) : ''
let textEndIndex = pattern.index
// Check if this pattern is an inline markdown link, hashtag, or relay URL that should be included in the paragraph
// Check if this pattern is an inline markdown link, hashtag, relay URL, or nostr address that should be included in the paragraph
// If so, extend the text to include the pattern so it gets processed as part of the paragraph
// This ensures links, hashtags, and relay URLs stay inline with their surrounding text instead of being separated
if (pattern.type === 'markdown-link' || pattern.type === 'hashtag' || pattern.type === 'relay-url') {
// This ensures links, hashtags, relay URLs, and nostr addresses stay inline with their surrounding text instead of being separated
// Note: Only profile types (npub/nprofile) should be merged inline; event types (note/nevent/naddr) remain block-level
if (pattern.type === 'markdown-link' || pattern.type === 'hashtag' || pattern.type === 'relay-url' || pattern.type === 'nostr') {
// Get the line containing the pattern
const lineStart = content.lastIndexOf('\n', pattern.index) + 1
const lineEnd = content.indexOf('\n', pattern.end)
@ -838,6 +839,25 @@ function parseMarkdownContent( @@ -838,6 +839,25 @@ 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
const bech32Id = pattern.data
const isProfileType = bech32Id.startsWith('npub') || bech32Id.startsWith('nprofile')
if (isProfileType) {
// Get the original pattern syntax from the content
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)
}
}
}
@ -1817,9 +1837,9 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st @@ -1817,9 +1837,9 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st
const hashtagMatches = Array.from(text.matchAll(hashtagRegex))
hashtagMatches.forEach(match => {
if (match.index !== undefined) {
// Skip if already in code, bold, italic, strikethrough, link, or relay-url
// Skip if already in code, bold, italic, strikethrough, link, relay-url, or nostr
const isInOther = inlinePatterns.some(p =>
(p.type === 'code' || p.type === 'bold' || p.type === 'italic' || p.type === 'strikethrough' || p.type === 'link' || p.type === 'hashtag' || p.type === 'relay-url') &&
(p.type === 'code' || p.type === 'bold' || p.type === 'italic' || p.type === 'strikethrough' || p.type === 'link' || p.type === 'hashtag' || p.type === 'relay-url' || p.type === 'nostr') &&
match.index! >= p.index &&
match.index! < p.end
)
@ -1841,9 +1861,9 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st @@ -1841,9 +1861,9 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st
const url = match[0]
// Only process if it's actually a websocket URL
if (isWebsocketUrl(url)) {
// Skip if already in code, bold, italic, strikethrough, link, or hashtag
// Skip if already in code, bold, italic, strikethrough, link, hashtag, or nostr
const isInOther = inlinePatterns.some(p =>
(p.type === 'code' || p.type === 'bold' || p.type === 'italic' || p.type === 'strikethrough' || p.type === 'link' || p.type === 'hashtag' || p.type === 'relay-url') &&
(p.type === 'code' || p.type === 'bold' || p.type === 'italic' || p.type === 'strikethrough' || p.type === 'link' || p.type === 'hashtag' || p.type === 'relay-url' || p.type === 'nostr') &&
match.index! >= p.index &&
match.index! < p.end
)
@ -1859,6 +1879,35 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st @@ -1859,6 +1879,35 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st
}
})
// Nostr addresses: nostr:npub1..., nostr:note1..., etc. (process after code/bold/italic/links/hashtags/relay-urls to avoid conflicts)
// Only process profile types (npub/nprofile) inline; event types (note/nevent/naddr) should remain block-level
const nostrRegex = /nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+|note1[a-z0-9]{58}|nevent1[a-z0-9]+|naddr1[a-z0-9]+)/g
const nostrMatches = Array.from(text.matchAll(nostrRegex))
nostrMatches.forEach(match => {
if (match.index !== undefined) {
const bech32Id = match[1]
// Only process profile types inline; event types should remain block-level
const isProfileType = bech32Id.startsWith('npub') || bech32Id.startsWith('nprofile')
if (isProfileType) {
// Skip if already in code, bold, italic, strikethrough, link, hashtag, or relay-url
const isInOther = inlinePatterns.some(p =>
(p.type === 'code' || p.type === 'bold' || p.type === 'italic' || p.type === 'strikethrough' || p.type === 'link' || p.type === 'hashtag' || p.type === 'relay-url' || p.type === 'nostr') &&
match.index! >= p.index &&
match.index! < p.end
)
if (!isInOther) {
inlinePatterns.push({
index: match.index,
end: match.index + match[0].length,
type: 'nostr',
data: bech32Id
})
}
}
}
})
// Sort by index
inlinePatterns.sort((a, b) => a.index - b.index)
@ -1947,6 +1996,20 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st @@ -1947,6 +1996,20 @@ function parseInlineMarkdown(text: string, keyPrefix: string, _footnotes: Map<st
{url}
</a>
)
} else if (pattern.type === 'nostr') {
// Render nostr addresses - only profile types (npub/nprofile) should be here (event types remain block-level)
const bech32Id = pattern.data
if (bech32Id.startsWith('npub') || bech32Id.startsWith('nprofile')) {
// Render as inline mention
parts.push(
<span key={`${keyPrefix}-nostr-${i}`} className="inline-block">
<EmbeddedMention userId={bech32Id} />
</span>
)
} else {
// Fallback for unexpected types (shouldn't happen, but handle gracefully)
parts.push(<span key={`${keyPrefix}-nostr-${i}`}>nostr:{bech32Id}</span>)
}
}
lastIndex = pattern.end

Loading…
Cancel
Save