mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 10:52:22 +00:00
ProxMenux 1.2.8: release notes, changelog, image and version
This commit is contained in:
@@ -27,6 +27,17 @@ function resolveChangelogPath(locale: string): string {
|
||||
return path.join(repoRoot, "CHANGELOG.md")
|
||||
}
|
||||
|
||||
// The image of the latest release, which is what a link to this page shows
|
||||
// in RSS readers and social networks.
|
||||
function latestReleaseImage(locale: string): string | null {
|
||||
try {
|
||||
const content = fs.readFileSync(resolveChangelogPath(locale), "utf8")
|
||||
return content.match(/!\[[^\]]*\]\((https:[^)]+)\)/)?.[1] ?? null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Surface the latest changelog entry in the metadata so social previews and
|
||||
// SERP snippets reflect what was actually shipped. The CHANGELOG.md mixes two
|
||||
// formats — `## [x.y.z] - YYYY-MM-DD` (older releases) and `## YYYY-MM-DD`
|
||||
@@ -76,6 +87,7 @@ export async function generateMetadata({
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params
|
||||
const t = await getTranslations({ locale, namespace: "changelog.meta" })
|
||||
const releaseImage = latestReleaseImage(locale)
|
||||
const latest = readLatestChangelogVersion(locale)
|
||||
const versionTag = latest?.version ?? ""
|
||||
const dateTag = latest?.date ?? ""
|
||||
@@ -114,9 +126,9 @@ export async function generateMetadata({
|
||||
siteName: "ProxMenux",
|
||||
images: [
|
||||
{
|
||||
url: "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
|
||||
width: 1363,
|
||||
height: 735,
|
||||
url: releaseImage ?? "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
|
||||
width: releaseImage ? 1280 : 1363,
|
||||
height: releaseImage ? 640 : 735,
|
||||
alt: "ProxMenux — Interactive Menu and Web Dashboard for Proxmox VE",
|
||||
},
|
||||
],
|
||||
@@ -126,7 +138,7 @@ export async function generateMetadata({
|
||||
title: `${t("title")}${titleSuffix}`,
|
||||
description: `${t("ogDescription")} ${descriptionSuffix}`.trim(),
|
||||
images: [
|
||||
"https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
|
||||
releaseImage ?? "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ interface ChangelogEntry {
|
||||
// Per-locale RSS feed. Mirrors /app/rss.xml/route.ts (which stays the
|
||||
// canonical English feed at the root for backwards compatibility with
|
||||
// existing subscribers) but reads the localized CHANGELOG at
|
||||
// <repo>/lang/<locale>/CHANGELOG.md. Falls back to the English source
|
||||
// web/data/changelog/<locale>.md. Falls back to the English source
|
||||
// when the localized file doesn't exist yet so partial translations
|
||||
// still produce a valid feed.
|
||||
|
||||
@@ -61,7 +61,7 @@ const STRINGS: Record<string, LocaleStrings> = {
|
||||
function resolveChangelogPath(locale: string): string {
|
||||
const repoRoot = path.join(process.cwd(), "..")
|
||||
if (locale && locale !== "en") {
|
||||
const localized = path.join(repoRoot, "lang", locale, "CHANGELOG.md")
|
||||
const localized = path.join(process.cwd(), "data", "changelog", `${locale}.md`)
|
||||
if (fs.existsSync(localized)) return localized
|
||||
}
|
||||
return path.join(repoRoot, "CHANGELOG.md")
|
||||
@@ -81,6 +81,17 @@ function extractFirstImage(rawContent: string): string | null {
|
||||
return `https://proxmenux.com/${url}`
|
||||
}
|
||||
|
||||
// Size of a release image of this repository, which an enclosure has to state.
|
||||
function imageBytes(url: string): number | null {
|
||||
const name = url.match(/\/images\/([^/]+)$/)?.[1]
|
||||
if (!name) return null
|
||||
try {
|
||||
return fs.statSync(path.join(process.cwd(), "..", "images", name)).size
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function escapeXml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
@@ -225,15 +236,15 @@ export async function GET(
|
||||
(entry) => `
|
||||
<item>
|
||||
<title>${escapeXml(entry.title)}</title>
|
||||
<description>${escapeXml(entry.content.replace(/<[^>]*>/g, "").substring(0, 200))}...</description>
|
||||
<description><![CDATA[${entry.image ? `<img src="${entry.image}" alt="${escapeXml(entry.title)}"/><br/>` : ""}${entry.content.replace(/<[^>]*>/g, "").substring(0, 200)}...]]></description>
|
||||
<content:encoded><![CDATA[${entry.content}]]></content:encoded>
|
||||
<link>${entry.url}</link>
|
||||
<guid isPermaLink="true">${entry.url}</guid>
|
||||
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>
|
||||
<category>${escapeXml(strings.category)}</category>${entry.image ? `
|
||||
<media:thumbnail url="${escapeXml(entry.image)}"/>
|
||||
<media:content url="${escapeXml(entry.image)}" medium="image"/>
|
||||
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="0"/>` : ""}
|
||||
<media:content url="${escapeXml(entry.image)}" medium="image" type="image/png"/>${imageBytes(entry.image) ? `
|
||||
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="${imageBytes(entry.image)}"/>` : ""}` : ""}
|
||||
</item>`,
|
||||
)
|
||||
.join("")}
|
||||
|
||||
@@ -33,6 +33,17 @@ function extractFirstImage(rawContent: string): string | null {
|
||||
return `https://proxmenux.com/${url}`
|
||||
}
|
||||
|
||||
// Size of a release image of this repository, which an enclosure has to state.
|
||||
function imageBytes(url: string): number | null {
|
||||
const name = url.match(/\/images\/([^/]+)$/)?.[1]
|
||||
if (!name) return null
|
||||
try {
|
||||
return fs.statSync(path.join(process.cwd(), "..", "images", name)).size
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function escapeXml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
@@ -180,15 +191,15 @@ export async function GET() {
|
||||
(entry) => `
|
||||
<item>
|
||||
<title>${escapeXml(entry.title)}</title>
|
||||
<description>${escapeXml(entry.content.replace(/<[^>]*>/g, '').substring(0, 200))}...</description>
|
||||
<description><![CDATA[${entry.image ? `<img src="${entry.image}" alt="${escapeXml(entry.title)}"/><br/>` : ""}${entry.content.replace(/<[^>]*>/g, '').substring(0, 200)}...]]></description>
|
||||
<content:encoded><![CDATA[${entry.content}]]></content:encoded>
|
||||
<link>${entry.url}</link>
|
||||
<guid isPermaLink="true">${entry.url}</guid>
|
||||
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>
|
||||
<category>Changelog</category>${entry.image ? `
|
||||
<media:thumbnail url="${escapeXml(entry.image)}"/>
|
||||
<media:content url="${escapeXml(entry.image)}" medium="image"/>
|
||||
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="0"/>` : ""}
|
||||
<media:content url="${escapeXml(entry.image)}" medium="image" type="image/png"/>${imageBytes(entry.image) ? `
|
||||
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="${imageBytes(entry.image)}"/>` : ""}` : ""}
|
||||
</item>`,
|
||||
)
|
||||
.join("")}
|
||||
|
||||
Reference in New Issue
Block a user