'use client'
import * as React from 'react'
import { Marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
import 'highlight.js/styles/github-dark.css'
interface ReadmeViewerProps {
content: string
repositoryUrl?: string
}
export function ReadmeViewer({ content, repositoryUrl }: ReadmeViewerProps) {
const [html, setHtml] = React.useState('')
React.useEffect(() => {
const marked = new Marked(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
return hljs.highlight(code, { language }).value
},
})
)
// Configure marked options
marked.setOptions({
gfm: true,
breaks: true,
})
// Custom renderer to fix relative image URLs
if (repositoryUrl) {
const renderer = {
image(href: string, title: string | null, text: string) {
let imgSrc = href
// Convert GitHub URLs to raw content URLs
if (repositoryUrl.includes('github.com')) {
const match = repositoryUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/)
if (match) {
const [, owner, repo] = match
const cleanRepo = repo.replace(/\.git$/, '')
// Handle relative URLs
if (!imgSrc.startsWith('http://') && !imgSrc.startsWith('https://') && !imgSrc.startsWith('//')) {
// Remove leading ./
imgSrc = imgSrc.replace(/^\.\//, '')
// Build raw GitHub URL (main/master branch assumed)
imgSrc = `https://raw.githubusercontent.com/${owner}/${cleanRepo}/master/${imgSrc}`
}
// Handle GitHub blob URLs - convert to raw
else if (imgSrc.includes('github.com') && imgSrc.includes('/blob/')) {
imgSrc = imgSrc.replace('github.com', 'raw.githubusercontent.com').replace('/blob/', '/')
}
}
}
const titleAttr = title ? ` title="${title}"` : ''
// Check if it's a badge (shields.io, badgen, etc.)
const isBadge = imgSrc.includes('shields.io') ||
imgSrc.includes('badgen.net') ||
imgSrc.includes('badge') ||
imgSrc.includes('img.shields') ||
imgSrc.match(/\/badges?\//)
if (isBadge) {
return `
`
}
return `
`
}
}
marked.use({ renderer })
}
// Parse markdown
const parseMarkdown = async () => {
const result = await marked.parse(content)
setHtml(result)
}
parseMarkdown()
}, [content, repositoryUrl])
return (
)
}