From a8c287d0217bf18680037fa78a78b20b027414dd Mon Sep 17 00:00:00 2001 From: MacRimi Date: Tue, 27 May 2025 17:16:44 +0200 Subject: [PATCH] create RSS page --- web/app/api/rss/route.ts | 92 +++++++++++++++++++++++++++++++++++++ web/app/changelog/page.tsx | 36 ++++++++------- web/components/navbar.tsx | 27 ++++++++++- web/components/rss-link.tsx | 22 +++++++++ 4 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 web/app/api/rss/route.ts create mode 100644 web/components/rss-link.tsx diff --git a/web/app/api/rss/route.ts b/web/app/api/rss/route.ts new file mode 100644 index 0000000..a43f99d --- /dev/null +++ b/web/app/api/rss/route.ts @@ -0,0 +1,92 @@ +import { NextResponse } from "next/server" +import fs from "fs" +import path from "path" + +interface ChangelogEntry { + version: string + date: string + content: string + url: string +} + +async function parseChangelog(): Promise { + try { + const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md") + + if (!fs.existsSync(changelogPath)) { + return [] + } + + const fileContents = fs.readFileSync(changelogPath, "utf8") + const entries: ChangelogEntry[] = [] + + // Split content by versions (assuming format ## [version] - date) + const sections = fileContents.split(/^## /gm).filter((section) => section.trim()) + + for (const section of sections) { + const lines = section.split("\n") + const headerLine = lines[0] + + // Extract version and date from header + const versionMatch = headerLine.match(/\[([^\]]+)\]/) + const dateMatch = headerLine.match(/(\d{4}-\d{2}-\d{2})/) + + if (versionMatch) { + const version = versionMatch[1] + const date = dateMatch ? dateMatch[1] : new Date().toISOString().split("T")[0] + const content = lines.slice(1).join("\n").trim() + + entries.push({ + version, + date, + content, + url: `${process.env.NEXT_PUBLIC_SITE_URL || "https://macrimi.github.io/ProxMenux"}/changelog#${version}`, + }) + } + } + + return entries.slice(0, 10) // Latest 10 entries + } catch (error) { + console.error("Error parsing changelog:", error) + return [] + } +} + +export async function GET() { + const entries = await parseChangelog() + const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://macrimi.github.io/ProxMenux" + + const rssXml = ` + + + ProxMenux Changelog + Latest updates and changes in ProxMenux + ${siteUrl}/changelog + + en + ${new Date().toUTCString()} + ProxMenux RSS Generator + + ${entries + .map( + (entry) => ` + + ProxMenux ${entry.version} + + ${entry.url} + ${entry.url} + ${new Date(entry.date).toUTCString()} + Changelog + `, + ) + .join("")} + +` + + return new NextResponse(rssXml, { + headers: { + "Content-Type": "application/rss+xml; charset=utf-8", + "Cache-Control": "public, max-age=3600, s-maxage=3600", + }, + }) +} diff --git a/web/app/changelog/page.tsx b/web/app/changelog/page.tsx index b61aec0..d03c239 100644 --- a/web/app/changelog/page.tsx +++ b/web/app/changelog/page.tsx @@ -2,13 +2,13 @@ import fs from "fs" import path from "path" import { remark } from "remark" import html from "remark-html" -import * as gfm from "remark-gfm" // ✅ Asegura la correcta importación de `remark-gfm` +import * as gfm from "remark-gfm" import dynamic from "next/dynamic" -import React from "react" import parse from "html-react-parser" import Footer from "@/components/footer" +import RSSLink from "@/components/rss-link" -// 🔹 Importamos `CopyableCode` dinámicamente para evitar problemas de SSR +// Import CopyableCode dynamically to avoid SSR issues const CopyableCode = dynamic(() => import("@/components/CopyableCode"), { ssr: false }) async function getChangelogContent() { @@ -16,33 +16,33 @@ async function getChangelogContent() { const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md") if (!fs.existsSync(changelogPath)) { - console.error("❌ Archivo CHANGELOG.md no encontrado.") - return "

Error: No se encontró el archivo CHANGELOG.md

" + console.error("❌ CHANGELOG.md file not found.") + return "

Error: CHANGELOG.md file not found

" } const fileContents = fs.readFileSync(changelogPath, "utf8") - // ✅ Agregamos `remark-gfm` para permitir imágenes, tablas y otros elementos avanzados de Markdown + // Add remark-gfm to support images, tables and other advanced Markdown elements const result = await remark() - .use(gfm.default || gfm) // ✅ Manejo seguro de `remark-gfm` + .use(gfm.default || gfm) // Safe handling of remark-gfm .use(html) .process(fileContents) return result.toString() } catch (error) { - console.error("❌ Error al leer el archivo CHANGELOG.md", error) - return "

Error: No se pudo cargar el contenido del changelog.

" + console.error("❌ Error reading CHANGELOG.md file", error) + return "

Error: Could not load changelog content.

" } } -// 🔹 Limpia las comillas invertidas en fragmentos de código en línea +// Clean backticks in inline code fragments function cleanInlineCode(content: string) { return content.replace(/(.*?)<\/code>/g, (_, codeContent) => { return `${codeContent.replace(/^`|`$/g, "")}` }) } -// 🔹 Envuelve los bloques de código en +// Wrap code blocks with CopyableCode component function wrapCodeBlocksWithCopyable(content: string) { return parse(content, { replace: (domNode: any) => { @@ -53,20 +53,24 @@ function wrapCodeBlocksWithCopyable(content: string) { return } } - } + }, }) } export default async function ChangelogPage() { const changelogContent = await getChangelogContent() - const cleanedInlineCode = cleanInlineCode(changelogContent) // 🔹 Primero limpiamos código en línea - const parsedContent = wrapCodeBlocksWithCopyable(cleanedInlineCode) // 🔹 Luego aplicamos JSX a bloques de código + const cleanedInlineCode = cleanInlineCode(changelogContent) // First clean inline code + const parsedContent = wrapCodeBlocksWithCopyable(cleanedInlineCode) // Then apply JSX to code blocks return (
-
{/* 📌 Ajuste exacto como GitHub */} +
+ {" "} + {/* Exact adjustment like GitHub */}

Changelog

-
{parsedContent}
{/* 📌 Texto ajustado a 16px */} + {/* RSS Link Component */} + +
{parsedContent}
{/* Text adjusted to 16px */}
diff --git a/web/components/navbar.tsx b/web/components/navbar.tsx index 9eb34b3..18c1753 100644 --- a/web/components/navbar.tsx +++ b/web/components/navbar.tsx @@ -2,7 +2,7 @@ import Link from "next/link" import Image from "next/image" -import { Book, GitBranch, FileText, Github, Menu } from "lucide-react" +import { Book, GitBranch, FileText, Github, Menu, Rss } from "lucide-react" import { useState } from "react" export default function Navbar() { @@ -43,6 +43,18 @@ export default function Navbar() { {item.label} ))} + + {/* RSS Feed Link */} + + + RSS + {/* Mobile menu button */} @@ -66,6 +78,19 @@ export default function Navbar() { {item.label} ))} + + {/* RSS Feed Link - Mobile */} + setIsMenuOpen(false)} + target="_blank" + rel="noopener noreferrer" + title="RSS Feed del Changelog" + > + + RSS + )}
diff --git a/web/components/rss-link.tsx b/web/components/rss-link.tsx new file mode 100644 index 0000000..16b49ac --- /dev/null +++ b/web/components/rss-link.tsx @@ -0,0 +1,22 @@ +import { Rss } from "lucide-react" +import Link from "next/link" + +export default function RSSLink() { + return ( +
+
+

Stay Updated!

+

Subscribe to our RSS feed to get notified of new changes.

+
+ + + RSS Feed + +
+ ) +}