diff --git a/web/app/rss.xml/route.ts b/web/app/rss.xml/route.ts index eb8f174..5476940 100644 --- a/web/app/rss.xml/route.ts +++ b/web/app/rss.xml/route.ts @@ -10,39 +10,42 @@ interface ChangelogEntry { title: string } +function escapeXml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + function formatContentForRSS(content: string): string { return ( content .replace(/https:\/\/macrimi\.github\.io\/ProxMenux/g, "https://proxmenux.com") .replace(/`([^`]+)`/g, "$1") - .replace(/!\[([^\]]*)\]$$([^)]+)$$/g, (match, alt, url) => { - // Convert relative URLs to absolute + .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, url) => { let absoluteUrl = url if (url.startsWith("/")) { absoluteUrl = `https://proxmenux.com${url}` } else if (!url.startsWith("http://") && !url.startsWith("https://")) { - // Relative path, make it absolute absoluteUrl = `https://proxmenux.com/${url}` } - return `${alt}` + return `
+ ${alt} +
` }) - // Convert markdown links to HTML - .replace(/\[([^\]]+)\]$$([^)]+)$$/g, '$1') - // Convert ### headers to

tags + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') .replace(/^### (.+)$/gm, "

$1

") - // Convert ** bold ** to tags .replace(/\*\*(.*?)\*\*/g, "$1") - // Convert code blocks to
 tags
       .replace(/```[\s\S]*?```/g, (match) => {
         const code = match.replace(/```/g, "").trim()
         return `
${code}
` }) - // Convert - bullet points to
  • tags .replace(/^- (.+)$/gm, "
  • $1
  • ") - // Wrap consecutive
  • tags in
      .replace(/(
    • .*?<\/li>\s*)+/g, (match) => `
        ${match}
      `) - .replace(/\n/g, "
      ") - // Clean up extra spaces + .replace(/^---$/gm, '
      ') + .replace(/\n/g, "
      ") .replace(/\s+/g, " ") .trim() ) @@ -59,20 +62,15 @@ async function parseChangelog(): Promise { const fileContents = fs.readFileSync(changelogPath, "utf8") const entries: ChangelogEntry[] = [] - // Split by ## headers (both versions and dates) const lines = fileContents.split("\n") let currentEntry: Partial | null = null let contentLines: string[] = [] for (const line of lines) { - // Check for version header: ## [1.1.1] - 2025-03-21 const versionMatch = line.match(/^##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/) - - // Check for date-only header: ## 2025-05-13 const dateMatch = line.match(/^##\s+(\d{4}-\d{2}-\d{2})$/) if (versionMatch || dateMatch) { - // Save previous entry if exists if (currentEntry && contentLines.length > 0) { const rawContent = contentLines.join("\n").trim() currentEntry.content = formatContentForRSS(rawContent) @@ -81,7 +79,6 @@ async function parseChangelog(): Promise { } } - // Start new entry if (versionMatch) { const version = versionMatch[1] const date = versionMatch[2] @@ -103,14 +100,12 @@ async function parseChangelog(): Promise { contentLines = [] } else if (currentEntry && line.trim()) { - // Add content lines (skip empty lines at the beginning) if (contentLines.length > 0 || line.trim() !== "") { contentLines.push(line) } } } - // Don't forget the last entry if (currentEntry && contentLines.length > 0) { const rawContent = contentLines.join("\n").trim() currentEntry.content = formatContentForRSS(rawContent) @@ -119,7 +114,7 @@ async function parseChangelog(): Promise { } } - return entries.slice(0, 20) // Latest 20 entries + return entries.slice(0, 20) } catch (error) { console.error("Error parsing changelog:", error) return [] @@ -146,8 +141,9 @@ export async function GET() { .map( (entry) => ` - ${entry.title} - + ${escapeXml(entry.title)} + ${escapeXml(entry.content.replace(/<[^>]*>/g, '').substring(0, 200))}... + ${entry.url} ${entry.url} ${new Date(entry.date).toUTCString()} @@ -164,4 +160,4 @@ export async function GET() { "Cache-Control": "public, max-age=3600, s-maxage=3600", }, }) -} +} \ No newline at end of file