Skip to content

Commit afb9571

Browse files
committed
feat: improved security advisory pages
1 parent 7f18652 commit afb9571

File tree

1 file changed

+78
-29
lines changed

1 file changed

+78
-29
lines changed

src/app/security/[advisory]/page.tsx

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { readFileSync } from 'fs'
33
import { join } from 'path'
44
import PageLayout from '@/components/PageLayout'
55
import { generatePageMetadata } from '@/components/PageLayout'
6+
import { Metadata } from 'next'
67

78
interface SecurityAdvisoryPageProps {
89
params: Promise<{
@@ -39,34 +40,82 @@ function getAdvisoryContent(advisory: string): { title: string; description: str
3940

4041
// Function to convert Markdown content to JSX
4142
function parseAdvisoryContent(content: string): React.JSX.Element {
42-
// Convert markdown to HTML-like content for rendering
43-
let processedContent = content
44-
// Convert headers
45-
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
46-
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
47-
.replace(/^#### (.+)$/gm, '<h4>$1</h4>')
48-
49-
// Convert bold metadata format to definition list
50-
.replace(/^\*\*(.+?):\*\* (.+)$/gm, (match, term, definition) => {
51-
return `<dl class="row"><dt class="col-sm-3">${term}:</dt><dd class="col-sm-9">${definition}</dd></dl>`
52-
})
53-
54-
// Convert paragraphs (lines that don't start with special characters)
55-
.split('\n')
56-
.map(line => {
57-
line = line.trim()
58-
if (!line) return ''
59-
if (line.startsWith('<')) return line // Already HTML
60-
if (line.startsWith('#')) return line // Headers will be processed later
61-
if (line.startsWith('**')) return line // Metadata will be processed later
62-
return `<p>${line}</p>`
63-
})
64-
.join('\n')
65-
66-
// Clean up
67-
.replace(/\n+/g, '\n')
68-
.trim()
69-
43+
// Robust parser: group consecutive metadata lines into a single <dl class="dl-horizontal">
44+
const lines = content.split('\n')
45+
46+
const htmlParts: string[] = []
47+
let inDl = false
48+
49+
const closeDlIfOpen = () => {
50+
if (inDl) {
51+
htmlParts.push('</dl>')
52+
inDl = false
53+
}
54+
}
55+
56+
for (let line of lines) {
57+
let l = line.trim()
58+
59+
// Skip empty lines, but ensure we close an open DL
60+
if (l.length === 0) {
61+
closeDlIfOpen()
62+
continue
63+
}
64+
65+
// Keep raw HTML intact
66+
if (l.startsWith('<')) {
67+
closeDlIfOpen()
68+
htmlParts.push(l)
69+
continue
70+
}
71+
72+
// Headings
73+
if (l.startsWith('#### ')) {
74+
closeDlIfOpen()
75+
htmlParts.push(`<h4>${l.substring(5)}</h4>`)
76+
continue
77+
}
78+
if (l.startsWith('### ')) {
79+
closeDlIfOpen()
80+
htmlParts.push(`<h3>${l.substring(4)}</h3>`)
81+
continue
82+
}
83+
if (l.startsWith('## ')) {
84+
closeDlIfOpen()
85+
htmlParts.push(`<h2>${l.substring(3)}</h2>`)
86+
continue
87+
}
88+
89+
// Metadata lines like **Issued on::** 2004-07-27 or **Risk:** medium
90+
// Strategy: capture bold label and the value; strip trailing colons from label
91+
const metaMatch = l.match(/^\*\*(.+?)\*\*\s*(.+)$/)
92+
if (metaMatch) {
93+
let labelRaw = metaMatch[1].trim()
94+
const value = metaMatch[2].trim()
95+
96+
// Accept labels with one or more trailing colons inside the bold
97+
labelRaw = labelRaw.replace(/:+\s*$/, '').trim()
98+
99+
if (!inDl) {
100+
htmlParts.push('<dl class="dl-horizontal">')
101+
inDl = true
102+
}
103+
104+
htmlParts.push(`<dt>${labelRaw}:</dt><dd>${value}</dd>`)
105+
continue
106+
}
107+
108+
// Default: wrap as paragraph
109+
closeDlIfOpen()
110+
htmlParts.push(`<p>${l}</p>`)
111+
}
112+
113+
// Close any open DL at the end
114+
if (inDl) {
115+
htmlParts.push('</dl>')
116+
}
117+
118+
const processedContent = htmlParts.join('\n').replace(/\n+/g, '\n').trim()
70119
return <div dangerouslySetInnerHTML={{ __html: processedContent }} />
71120
}
72121

@@ -85,7 +134,7 @@ export async function generateStaticParams() {
85134
}))
86135
}
87136

88-
export async function generateMetadata({ params }: SecurityAdvisoryPageProps) {
137+
export async function generateMetadata({ params }: SecurityAdvisoryPageProps): Promise<Metadata> {
89138
const { advisory } = await params
90139

91140
const advisoryData = getAdvisoryContent(advisory)

0 commit comments

Comments
 (0)