feat: long form articles
This commit is contained in:
parent
0f16ed8d46
commit
3950cbd9e6
13 changed files with 1706 additions and 22 deletions
90
src/components/Note/LongFormArticle/remarkNostr.ts
Normal file
90
src/components/Note/LongFormArticle/remarkNostr.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import type { PhrasingContent, Root, Text } from 'mdast'
|
||||
import type { Plugin } from 'unified'
|
||||
import { visit } from 'unist-util-visit'
|
||||
import { NostrNode } from './types'
|
||||
|
||||
const NOSTR_REGEX =
|
||||
/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 NOSTR_REFERENCE_REGEX =
|
||||
/\[[^\]]+\]\[(nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+|note1[a-z0-9]{58}|nevent1[a-z0-9]+|naddr1[a-z0-9]+))\]/g
|
||||
|
||||
export const remarkNostr: Plugin<[], Root> = () => {
|
||||
return (tree) => {
|
||||
visit(tree, 'text', (node: Text, index, parent) => {
|
||||
if (!parent || typeof index !== 'number') return
|
||||
|
||||
const text = node.value
|
||||
|
||||
// First, handle reference-style nostr links [text][nostr:...]
|
||||
const refMatches = Array.from(text.matchAll(NOSTR_REFERENCE_REGEX))
|
||||
// Then, handle direct nostr links that are not part of reference links
|
||||
const directMatches = Array.from(text.matchAll(NOSTR_REGEX)).filter((directMatch) => {
|
||||
return !refMatches.some(
|
||||
(refMatch) =>
|
||||
directMatch.index! >= refMatch.index! &&
|
||||
directMatch.index! < refMatch.index! + refMatch[0].length
|
||||
)
|
||||
})
|
||||
|
||||
// Combine and sort matches by position
|
||||
const allMatches = [
|
||||
...refMatches.map((match) => ({
|
||||
...match,
|
||||
type: 'reference' as const,
|
||||
bech32Id: match[2],
|
||||
rawText: match[0]
|
||||
})),
|
||||
...directMatches.map((match) => ({
|
||||
...match,
|
||||
type: 'direct' as const,
|
||||
bech32Id: match[1],
|
||||
rawText: match[0]
|
||||
}))
|
||||
].sort((a, b) => a.index! - b.index!)
|
||||
|
||||
if (allMatches.length === 0) return
|
||||
|
||||
const children: (Text | NostrNode)[] = []
|
||||
let lastIndex = 0
|
||||
|
||||
allMatches.forEach((match) => {
|
||||
const matchStart = match.index!
|
||||
const matchEnd = matchStart + match[0].length
|
||||
|
||||
// Add text before the match
|
||||
if (matchStart > lastIndex) {
|
||||
children.push({
|
||||
type: 'text',
|
||||
value: text.slice(lastIndex, matchStart)
|
||||
})
|
||||
}
|
||||
|
||||
// Create custom nostr node with type information
|
||||
const nostrNode: NostrNode = {
|
||||
type: 'nostr',
|
||||
data: {
|
||||
hName: 'nostr',
|
||||
hProperties: {
|
||||
bech32Id: match.bech32Id,
|
||||
rawText: match.rawText
|
||||
}
|
||||
}
|
||||
}
|
||||
children.push(nostrNode)
|
||||
|
||||
lastIndex = matchEnd
|
||||
})
|
||||
|
||||
// Add remaining text after the last match
|
||||
if (lastIndex < text.length) {
|
||||
children.push({
|
||||
type: 'text',
|
||||
value: text.slice(lastIndex)
|
||||
})
|
||||
}
|
||||
|
||||
// Type assertion to tell TypeScript these are valid AST nodes
|
||||
parent.children.splice(index, 1, ...(children as PhrasingContent[]))
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue